MVCC
MVCC (от англ. Multi-Version Concurrency Control, управление параллельным доступом с помощью многоверсионности) — это метод управления параллельными транзакциями в системах управления базами данных (СУБД), при котором каждой транзакции предоставляется «снимок» данных (snapshot) на определённый момент времени, а изменения, вносимые одной транзакцией, не видны другим транзакциям до их фиксации (commit). Основная цель MVCC — обеспечить согласованность данных при высокой степени параллелизма, минимизируя блокировки и конфликты между читающими и пишущими операциями.
История
Концепция многоверсионности была впервые предложена в 1970-х годах в исследовательских работах, посвящённых транзакционным системам. Значительный вклад в развитие MVCC внесли исследования Дэвида Рида (David Reed) в Массачусетском технологическом институте (MIT), который в 1978 году описал механизм управления параллельным доступом с использованием версий данных. В 1980-х годах идеи MVCC были реализованы в коммерческих СУБД, таких как Oracle (начиная с версии 3, 1983 год). В 1990-х и 2000-х годах MVCC стало стандартным подходом для многих реляционных СУБД (PostgreSQL, MySQL с InnoDB, Microsoft SQL Server — с опцией Snapshot isolation) и NoSQL-систем (CouchDB, MongoDB, Riak).
Основные принципы
MVCC основан на идее, что каждый объект данных (строка, запись) может иметь несколько версий, соответствующих различным транзакциям. Каждая версия помечена идентификатором транзакции (Transaction ID, XID), которая её создала, и временем жизни (когда версия становится неактивной). Основные принципы включают:
- Снимок (snapshot): Каждая транзакция видит согласованный срез базы данных на момент своего начала (или начала выполнения текущего оператора). Изменения, сделанные параллельными транзакциями, не видны до их фиксации.
- Версионность: При изменении записи старая версия не удаляется, а сохраняется вместе с новой. Это позволяет читающим транзакциям обращаться к старым версиям, не блокируя пишущие.
- Фиксация (commit): После фиксации транзакции её изменения становятся видимыми для последующих транзакций. Если транзакция откатывается (rollback), её версии помечаются как неактивные и впоследствии удаляются сборщиком мусора.
Механизм работы
Версионирование строк
В большинстве реализаций MVCC каждая строка таблицы (или документ в NoSQL) содержит скрытые служебные поля:
xmin(создающая транзакция) — идентификатор транзакции, которая вставила или последний раз обновила строку.xmax(удаляющая транзакция) — идентификатор транзакции, которая удалила или обновила строку (в случае обновления старая версия помечается как удалённая).- Дополнительные поля для хранения времени создания версии (например,
created_at) могут использоваться в некоторых СУБД.
Видимость версий
Для определения, видна ли версия строки текущей транзакции, используется проверка на основе идентификаторов транзакций:
- Если
xminверсии равен идентификатору текущей транзакции, версия видна (собственные изменения). - Если
xminсоответствует завершённой (committed) транзакции, версия видна, еслиxmaxне указывает на активную или зафиксированную транзакцию, которая сделала строку неактивной. - Если
xminсоответствует незавершённой транзакции (uncommitted), версия не видна для других транзакций.
Сборка мусора (vacuum, compaction)
Со временем в системе накапливаются устаревшие версии, которые уже не видны ни одной активной транзакции. Для их удаления используется механизм сборки мусора:
- В PostgreSQL — процесс
VACUUM, который маркирует устаревшие версии как свободное пространство и может физически удалять их. - В MySQL InnoDB — фоновый поток
purgeудаляет неактивные версии. - В NoSQL-системах (например, MongoDB) — компактизация при записи или в фоновом режиме.
Преимущества и недостатки
Преимущества
- Высокая производительность чтения: Читающие транзакции не блокируют пишущие и наоборот. Чтение старых версий не требует захвата блокировок.
- Согласованность без ожидания: Каждая транзакция видит фиксированный снимок данных, что устраняет аномалии «грязного чтения» (dirty read) и «неповторимого чтения» (non-repeatable read) при правильной настройке уровня изоляции.
- Параллелизм: Одновременно может выполняться множество транзакций без конфликтов на блокировках строк, что критично для высоконагруженных систем (например, веб-приложений).
Недостатки
- Накладные расходы на хранение: Хранение нескольких версий одной строки увеличивает объём данных. Для больших таблиц с частыми обновлениями требуется дополнительное дисковое пространство.
- Сложность сборки мусора: Устаревшие версии необходимо регулярно удалять; задержки могут приводить к «раздутию» таблиц (bloat) и снижению производительности.
- Снижение производительности записи: Создание новых версий и проверки видимости на больших объёмах данных может замедлять операции записи.
- Аномалии изоляции: При неправильном выборе уровня изоляции (например,
READ COMMITTEDвместоREPEATABLE READ) возможны ситуации «фантомного чтения» (phantom read) или «потери обновлений» (lost update).
Уровни изоляции и MVCC
MVCC поддерживает разные уровни изоляции транзакций, определённые стандартом SQL:
- Read Uncommitted: В некоторых СУБД (например, PostgreSQL) отсутствует, так как MVCC всегда предотвращает «грязное чтение».
- Read Committed: Каждый запрос в транзакции видит снимок данных на момент начала запроса. Это наиболее часто используемый уровень, при котором возможны «неповторимые чтения».
- Repeatable Read: Транзакция видит снимок на момент своего начала. Позволяет избежать «неповторимых чтений», но возможны «фантомные чтения» (зависит от реализации).
- Serializable: Высший уровень изоляции, при котором транзакции выполняются так, как если бы они были последовательными. В реализации на основе MVCC используется мониторинг конфликтов (predicate locking или оптимистическая блокировка).
В PostgreSQL для обеспечения уровня REPEATABLE READ также применяется запрет на изменение строк, которые были прочитаны или изменены другими транзакциями (Snapshot Isolation, SI). Для SERIALIZABLE используется Serializable Snapshot Isolation (SSI).
Реализации в популярных СУБД
PostgreSQL
PostgreSQL использует MVCC как основной механизм управления параллелизмом. Каждая транзакция получает уникальный идентификатор (XID). Система хранит несколько версий строк в основной таблице (heap). Для ускорения поиска видимых версий применяются индексные структуры. Сборка мусора реализована через фоновый процесс autovacuum. Уровни изоляции: READ COMMITTED (по умолчанию), REPEATABLE READ, SERIALIZABLE.
MySQL (InnoDB)
Движок InnoDB в MySQL реализует MVCC через систему версионирования с использованием rollback segment и undo log. При изменении строки старая версия помещается в undo log, а в главной таблице остаётся только текущая версия. Видимость строк определяется на основе transaction ID и списка активных транзакций (read view). InnoDB по умолчанию использует уровень изоляции REPEATABLE READ, который фактически реализует Snapshot Isolation.
Oracle Database
Oracle применяет MVCC в виде “Read Consistency” (согласованность чтения). Для каждой транзакции создаётся «снимок» данных на момент начала запроса. Старые версии хранятся в сегментах отката (undo segments). Oracle поддерживает уровни изоляции READ COMMITTED (по умолчанию), SERIALIZABLE и READ ONLY.
NoSQL-системы (MongoDB, CouchDB)
- MongoDB: Начиная с версии 4.0, поддерживает многодокументные транзакции с MVCC. Каждая запись хранит метки времени (timestamp) и версии. При чтении транзакция использует snapshot из WiredTiger (движок хранения). Уровни изоляции:
snapshot(аналогREPEATABLE READ) иlocal(слабая изоляция). - CouchDB: Все изменения данных сохраняются в виде новых версий документов (append-only). MVCC реализован через механизм
multi-versionс последующей репликацией и компактизацией. Каждый документ имеет поле_rev— идентификатор версии.
Применение и значение
MVCC широко применяется в веб-приложениях, системах электронной коммерции, банковских системах и других областях, где требуется высокая пропускная способность операций чтения-записи при сохранении согласованности. Метод позволяет строить масштабируемые системы с минимальным количеством блокировок, что особенно важно для современных облачных и распределённых баз данных. Без MVCC большинство современных реляционных и NoSQL-СУБД не смогли бы обеспечивать заявленную производительность при многочисленных параллельных запросах.
Критика и ограничения
- Хранение метаданных: Служебные поля (xmin, xmax) требуют дополнительного места и процессорного времени на проверки, что может быть критично для систем с жёсткими требованиями к задержкам.
- Фрагментация: Частая сборка мусора и создание версий могут приводить к фрагментации данных на диске, что снижает эффективность индексного сканирования.
- Совместимость с блокировками: В некоторых сценариях (например, при конкурентном обновлении одной строки) MVCC не устраняет полностью необходимость в блокировках, что может приводить к взаимоблокировкам (deadlocks).
Тем не менее MVCC остаётся доминирующей парадигмой управления параллелизмом в базах данных XXI века благодаря балансу между производительностью и согласованностью.
Источники
- Том Кил, «Проектирование и реализация баз данных. Взгляд программиста» (Designing Data-Intensive Applications), 2017 (перевод на русский — «Проектирование распределённых систем»).
- Документация PostgreSQL (раздел «Multiversion Concurrency Control»). Официальный сайт postgresql.org.
- Документация MySQL InnoDB (раздел «InnoDB Multi-Versioning»). Официальный сайт dev.mysql.com.
- Документация Oracle Database (раздел «Read Consistency and Multiversion Concurrency Control»). Официальный сайт docs.oracle.com.
- Дэвид Рид, «Naming and Synchronization in a Decentralized Computer System» (MIT, 1978).
BFOmetr — база данных и аналитика по компаниям России.
На главную BFOmetr →