Объясните механизм: как MVCC позволяет транзакции читать согласованный снимок данных, не блокируя одновременно изменяющую их транзакцию?
MVCC хранит несколько версий изменяемой строки. Читающая транзакция выбирает версию, видимую в её снимке, поэтому обычно не ждёт завершения записи, а записывающая транзакция создаёт новую версию вместо немедленной замены старой.
Согласованность определяется правилами видимости: учитываются момент создания версии, идентификатор транзакции и факт её фиксации. Конкретные детали зависят от СУБД и выбранного уровня изоляции.
Традиционная модель блокировок заставляет читателя и писателя конкурировать за одну строку: читатель берёт разделяемую блокировку, а запись требует исключительную. При большом числе чтений это увеличивает ожидание и снижает пропускную способность.
MVCC, то есть многоверсионное управление конкурентным доступом, решает исходную проблему за счёт хранения старых состояний данных. Читатель получает логически согласованную версию, пока другая транзакция формирует новую, вместо обязательного ожидания её завершения.
Предположим, транзакция читает строку, которую другая транзакция уже изменила, но ещё не зафиксировала. Чтение незакоммиченной версии привело бы к грязному чтению: после отката писателя прочитанное значение никогда не существовало в подтверждённом состоянии.
Поэтому MVCC должен одновременно решить две задачи: не показать незавершённые изменения и сохранить для читателя подходящую старую версию. Неверная реализация или неправильное понимание режима изоляции может привести к устаревшим данным, неожиданным повторным чтениям, конфликтам записи или росту служебного хранения.
При обновлении строки MVCC обычно сохраняет предыдущую версию и создаёт новую. Версии связываются с информацией о транзакции, которая их создала, а иногда — с диапазоном видимости или ссылкой на предыдущую версию.
Читающая транзакция формирует снимок. При выборе строки СУБД проверяет, была ли версия создана уже завершившейся транзакцией к моменту снимка, не была ли она удалена видимой транзакцией и не относится ли её создание к незавершённой или более поздней транзакции. Если текущая версия невидима, СУБД переходит по цепочке к подходящей старой версии.
Писатель обычно не перезаписывает значение, которое должен видеть уже начавшийся читатель. Он создаёт новое состояние, а читатель продолжает работать со старым. Поэтому чтения и записи часто выполняются параллельно, но это не означает отсутствия блокировок вообще: блокироваться могут сами записи, конфликты транзакций, структурные операции или служебные ресурсы.
Важно различать снимок и отсутствие ограничений. В режиме Snapshot Isolation повторные чтения обычно видят один и тот же снимок, но конфликт между независимыми строками может остаться незамеченным. Кроме того, некоторые СУБД используют MVCC как внутренний механизм, но предлагают разные уровни изоляции и разные правила фиксации конфликтующих записей.
Старые версии нельзя удалять сразу после обновления. Они нужны активным транзакциям, чей снимок ещё может ссылаться на них. Фоновая очистка версий освобождает место после завершения таких транзакций; поэтому долго живущая транзакция способна увеличить объём хранения и нагрузку на очистку.
Главный компромисс таков: MVCC уменьшает блокирование читателей, но требует хранения версий, их очистки и дополнительных проверок видимости. Для бизнес-правил, охватывающих несколько строк, одного согласованного снимка недостаточно: требуются блокировки, ограничения, сериализуемая изоляция или явная проверка конфликтов.
В отчётном сервисе долгий запрос читает зафиксированные заказы, пока рабочая транзакция изменяет те же строки. При обычной блокирующей схеме отчёт может задерживать обновления или сам надолго ждать. При MVCC отчёт получает согласованный снимок и не мешает записи, но долго живущий снимок удерживает старые версии.
Рассматривались три варианта. Полностью блокирующее чтение проще объяснить, но оно ухудшает задержки обновлений. MVCC с короткими транзакциями сохраняет параллелизм, однако требует контроля длительности запросов и фоновой очистки. Чтение с максимально свежими данными уменьшает устаревание результата, но может дать разные версии при повторных обращениях внутри одной операции.
Выбран MVCC-снимок для одного отчёта, ограничение времени его выполнения и мониторинг долгих транзакций. Для экранов, которым нужна актуальная информация, применили отдельные короткие чтения. В результате отчёты перестали блокировать рабочие обновления, а риск разрастания хранилища контролируется ограничением времени жизни снимков.
1. Обеспечивает ли MVCC чтение только зафиксированных данных?
Да, корректно настроенное чтение по MVCC не должно показывать незакоммиченные версии. СУБД выбирает последнюю подходящую версию, созданную транзакцией, изменения которой видимы данному снимку.
Однако слово «последняя» означает последнюю видимую, а не обязательно физически последнюю версию. Если более новая версия создана незавершённой или началась после снимка, читатель может получить более старое состояние.
2. Почему MVCC не устраняет необходимость блокировок при записи?
Две транзакции могут одновременно изменить одну логическую сущность. СУБД должна определить, допустимы ли эти изменения, какая транзакция победит или нужно ли отклонить одну из них; иначе результат зависел бы от порядка завершения и мог бы потерять обновление.
Поэтому MVCC обычно дополняется блокировками строк, проверкой конфликтов или механизмом сериализации. Отсутствие блокирования читателей не означает, что писатели всегда могут изменять одну строку без ожидания или отката.
3. Чем опасна долгая транзакция при MVCC?
Её снимок может продолжать ссылаться на старые версии, даже если новые транзакции уже давно их не используют. Очиститель не может безопасно удалить такие версии, пока потенциальный читатель остаётся активным.
Следствия — рост служебного пространства, дополнительное чтение цепочек версий и увеличение стоимости очистки. Поэтому контролируют длительность транзакций, не удерживают их во время сетевого взаимодействия и отслеживают самые старые активные снимки.