# MVCC

> Многоверсионное управление параллельным доступом — СУБД хранит несколько версий строк, чтобы каждая транзакция видела согласованный снимок данных, а чтение не блокировало запись.

Источник: https://llmdocs.ru/wiki/mvcc/ · обновлено 2026-11-09 · лицензия CC BY-SA 4.0

- **Полное название**: Multiversion Concurrency Control
- **Идея**: каждая запись создаёт новую версию, старые остаются для текущих читателей
- **Теория**: Дэвид Рид (1978), Бернстайн и Гудман (1981)
- **Главное свойство**: читатели не блокируют писателей, писатели — читателей
- **Хранение версий**: в таблице (PostgreSQL) или в журнале отката (Oracle, MySQL InnoDB)
- **Очистка**: VACUUM (PostgreSQL), purge (InnoDB)
**MVCC** (англ. *Multiversion Concurrency Control* — многоверсионное управление параллельным доступом) — метод, при котором СУБД хранит несколько версий одной и той же строки. Изменение не перезаписывает данные на месте, а создаёт новую версию; старая остаётся видимой транзакциям, которые начались раньше. Каждая транзакция работает со своим согласованным **снимком** базы, и главное следствие: **читатели не блокируют писателей, а писатели — читателей**.[1]

MVCC используют [PostgreSQL](https://llmdocs.ru/wiki/postgresql.md), Oracle, [MySQL InnoDB](https://llmdocs.ru/wiki/mysql-mariadb.md), SQL Server (в режиме снимков), CockroachDB и многие другие системы. На нём основаны [уровни изоляции](https://llmdocs.ru/wiki/isolation-levels.md) Read Committed и Repeatable Read в этих СУБД.

## Зачем

Альтернатива — блокировки: чтобы прочитать строку, транзакция берёт разделяемую блокировку, чтобы изменить — исключительную. Тогда долгий аналитический запрос блокирует обновления, а массовое обновление останавливает отчёты. MVCC снимает этот конфликт: отчёт читает данные на момент своего начала, не мешая текущей работе. Блокировки остаются только между пишущими транзакциями, которые меняют одни и те же строки.

Теоретические основы заложили диссертация Дэвида Рида (MIT, 1978) и обзор Филипа Бернстайна и Натана Гудмана (1981);[3] одной из первых коммерческих систем с MVCC стала InterBase в 1980-х.

## Как это устроено в PostgreSQL

Каждая версия строки (*кортеж*) хранит в заголовке служебные поля:[6]

- **`xmin`** — номер транзакции, создавшей версию;
- **`xmax`** — номер транзакции, удалившей или заменившей её (0, если версия актуальна).

`UPDATE` в PostgreSQL — это пометка старой версии как удалённой (запись `xmax`) и вставка новой. При чтении транзакция по своему снимку — списку завершённых и ещё активных транзакций — решает, какая версия ей видна: созданная до начала снимка зафиксированной транзакцией и не удалённая в пределах видимого.

```sql
SELECT xmin, xmax, * FROM accounts WHERE id = 1;
-- xmin | xmax | id | balance
-- 7381 |    0 |  1 |   1000
UPDATE accounts SET balance = 900 WHERE id = 1;
SELECT xmin, xmax, * FROM accounts WHERE id = 1;
-- 7382 |    0 |  1 |    900     -- новая версия; старая осталась в таблице
```

## Очистка: VACUUM

Старые версии, которые уже не нужны ни одной транзакции, занимают место — это **«мёртвые» кортежи**. Их убирает процесс **VACUUM**, в нормальном режиме — фоновый **autovacuum**.[2] Если очистка не успевает, таблицы и индексы **раздуваются** (*bloat*): занимают в разы больше места и медленнее читаются.

Вторая задача VACUUM — **заморозка** старых строк. Номера транзакций в PostgreSQL 32-битные и идут по кругу; если строки вовремя не пометить как «видимые всем», произойдёт переполнение счётчика (*transaction ID wraparound*), и СУБД остановит запись, чтобы не потерять данные. Поэтому autovacuum никогда не стоит отключать.

Оптимизация **HOT** (*heap-only tuples*): если обновление не затрагивает индексированные столбцы и на странице есть место, новая версия кладётся рядом, и индексы не меняются.

## Другой подход: журнал отката

В Oracle и MySQL InnoDB актуальная версия строки хранится на месте, а предыдущие — в отдельном **журнале отката** (*undo log*). Транзакция, которой нужна старая версия, восстанавливает её, проходя по цепочке записей отката.[4]

| | В таблице (PostgreSQL) | В журнале отката (Oracle, InnoDB) |
|---|---|---|
| Обновление | новая версия в таблице | изменение на месте + запись в журнал |
| Откат транзакции | мгновенный | нужно применить журнал |
| Чтение старых версий | напрямую | восстановление из журнала |
| Обслуживание | VACUUM, раздувание таблиц | очистка журнала, долгие транзакции раздувают его |

Сравнение разных реализаций MVCC с замерами — в исследовании Ву и соавторов.[5]

## Практические следствия

- **Долгие транзакции вредны.** Пока открыта старая транзакция (в том числе «зависшая» `idle in transaction`), VACUUM не может удалить версии, которые она теоретически может увидеть, и таблицы раздуваются. Их стоит отслеживать в `pg_stat_activity` и ограничивать параметром `idle_in_transaction_session_timeout`.
- **Частые обновления одной строки** (счётчики, статусы) порождают много мёртвых версий — для таких таблиц настраивают более агрессивный autovacuum.
- **`count(*)` по большой таблице** в PostgreSQL требует проверки видимости строк и не бывает мгновенным.
- **Конфликты записи остаются.** MVCC не отменяет блокировок между пишущими транзакциями; для правил, затрагивающих несколько строк, нужны явные блокировки или уровень Serializable (см. [Транзакция и ACID](https://llmdocs.ru/wiki/transaction-acid.md)).

## См. также

- [Уровни изоляции транзакций](https://llmdocs.ru/wiki/isolation-levels.md)
- [Транзакция и ACID](https://llmdocs.ru/wiki/transaction-acid.md)
- [PostgreSQL](https://llmdocs.ru/wiki/postgresql.md)
- [Индекс (базы данных)](https://llmdocs.ru/wiki/database-index.md)
- [Репликация баз данных](https://llmdocs.ru/wiki/replication.md)

## Источники

1. [PostgreSQL Documentation — Concurrency Control: Introduction](https://www.postgresql.org/docs/current/mvcc-intro.html)
2. [PostgreSQL Documentation — Routine Vacuuming](https://www.postgresql.org/docs/current/routine-vacuuming.html)
3. [P. Bernstein, N. Goodman — Concurrency Control in Distributed Database Systems (ACM Computing Surveys, 1981)](https://dl.acm.org/doi/10.1145/356842.356846)
4. [MySQL — InnoDB Multi-Versioning](https://dev.mysql.com/doc/refman/8.4/en/innodb-multi-versioning.html)
5. [Y. Wu et al. — An Empirical Evaluation of In-Memory Multi-Version Concurrency Control (VLDB 2017)](https://www.vldb.org/pvldb/vol10/p781-Wu.pdf)
6. [PostgreSQL Documentation — Database Page Layout](https://www.postgresql.org/docs/current/storage-page-layout.html)
