MVCC
MVCC (Multi-Version Concurrency Control) — принцип конкурентного доступа: каждая запись хранит несколько версий. Читатели не блокируют писателей, писатели не блокируют читателей. Основа PostgreSQL, InnoDB (MySQL), Oracle, CockroachDB, MongoDB.
Идея
# без MVCC (2PL locking)
Читатель ждёт пока писатель закончит.
Писатель ждёт пока читатели закончат.
→ contention, deadlocks
# с MVCC
Каждая запись имеет версии:
row_v1 (created_by tx=10, deleted_by tx=15)
row_v2 (created_by tx=15, deleted_by NULL)
Читатель tx=12 видит row_v1 (v2 ещё не был закоммичен на момент tx=12)
Писатель tx=15 создал row_v2 (первый — не блокирует)
Snapshot isolation
Транзакция получает snapshot базы на момент начала. Все SELECT в транзакции видят одно и то же состояние. Даже если параллельно кто-то закоммитил.
PostgreSQL реализация
# каждая tuple имеет
xmin — tx что создал
xmax — tx что удалил (или NULL)
# видимость для tx N:
1. xmin закоммичен и xmin < N
2. И (xmax IS NULL или xmax НЕ закоммичен или xmax > N)
Старые версии не удаляются сразу — их убирает VACUUM.
InnoDB (MySQL)
Undo log вместо inline-версий. Актуальная строка в таблице. Старые версии реконструируются по undo log обратными операциями. Быстрее для UPDATE, медленнее для long-running SELECT.
Плюсы MVCC
- Readers не блокируют writers
- Consistent snapshot для отчётов
- Time travel (в некоторых БД)
- Меньше deadlocks
Минусы
- Больше диска (версии копятся)
- Нужен VACUUM / GC старых версий
- Bloat — таблицы растут даже без роста данных
- Long transactions не дают удалить старые версии → bloat
Write skew (не защищает всё)
Snapshot isolation ≠ serializable. Классический пример: write skew — две транзакции читают одни данные, пишут не пересекающиеся, но нарушают инвариант.
# В любой момент должен быть хотя бы 1 доктор on-call
T1: SELECT COUNT(*) → 2, UPDATE me SET on_call = false
T2: SELECT COUNT(*) → 2, UPDATE her SET on_call = false
# оба коммитятся — 0 докторов on-call
Решение: SERIALIZABLE или SELECT FOR UPDATE.