Почему мультиверсионность (MVCC) мешает реализации уровня изоляции Read Uncommitted?

Ответ

Механизм Multiversion Concurrency Control (MVCC) и уровень изоляции READ UNCOMMITTED концептуально противоречат друг другу, что делает их совместную реализацию бессмысленной или крайне неэффективной.

Суть противоречия:

  • Цель READ UNCOMMITTED: Позволить транзакции видеть незафиксированные изменения (dirty reads) других параллельных транзакций, жертвуя целостностью данных ради максимальной скорости чтения.
  • Принцип MVCC: Каждая операция чтения (транзакция) работает с согласованным снимком данных (snapshot) на определённый момент времени (обычно на момент начала транзакции или оператора). Этот снимок содержит только те изменения, которые были уже зафиксированы к этому моменту.

Почему MVCC «мешает»? MVCC по своей природе изолирует читающую транзакцию от незавершённых изменений. Чтобы реализовать READ UNCOMMITTED поверх MVCC, системе пришлось бы:

  1. Отказаться от основного преимущества MVCC — предоставления читателям стабильного снимка без блокировок.
  2. Начать отслеживать и предоставлять доступ к незафиксированным версиям строк, что резко усложняет механизм управления версиями.
  3. По сути, внедрить дополнительный, противоречащий основной логике, механизм для поддержки поведения, которое считается антипаттерном с точки зрения целостности данных.

Техническая иллюстрация:

-- Транзакция 1 (Модификация)
BEGIN TRANSACTION; -- Создаётся новая версия строки для этой транзакции (V_new).
UPDATE Users SET Balance = 1000 WHERE Id = 1; -- V_new не зафиксирована.

-- Транзакция 2 (Чтение с уровнем READ UNCOMMITTED в системе с MVCC)
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN TRANSACTION;
-- При MVCC: Транзакция 2 получит снимок на момент своего начала.
-- В снимок попадает только последняя ЗАФИКСИРОВАННАЯ версия строки (V_old).
SELECT Balance FROM Users WHERE Id = 1; -- Вернёт старое значение, а не 1000.
-- Чтобы увидеть 1000, системе пришлось бы специально искать незафиксированную V_new,
-- что нарушает изоляцию снимка и не является стандартным поведением MVCC.
COMMIT;

Вывод: Системы, основанные на чистом MVCC (как PostgreSQL, Oracle), часто просто не реализуют READ UNCOMMITTED как отдельный уровень, либо он семантически работает идентично READ COMMITTED. Реализация истинных «грязных чтений» требует подхода, основанного на блокировках (как в SQL Server), а не на версионности.

Ответ 18+ 🔞

Слушай, давай разжую эту тему, как будто мы на кухне сидим и я пытаюсь объяснить, почему не надо совать вилку в розетку.

Представь себе, что MVCC — это такой идеальный, ебанутый библиотекарь. У него на каждую книжку (строку в базе) есть целая хронология: вот оригинал, вот исправленная версия от Васька, вот правки от Петьки, но Петька свои ещё не сдал. Когда ты приходишь и говоришь: «Дайте мне почитать про баланс пользователя №1», этот библиотекарь смотрит на часы (на момент начала твоей транзакции), лезет в свой журнал и выдаёт тебе ту версию, которая была последней и сданной в архив к этому времени. Незавершённые правки Петьки он тебе не покажет, потому что Петька мог их ещё и выкинуть. Это и есть READ COMMITTED по сути — читаешь только зафиксированное.

А теперь приходишь ты, такой хитрожопый, и заявляешь: «А дайте-ка мне уровень READ UNCOMMITTED! Я хочу видеть, что там Петька прямо сейчас карябает в своей черновике, не дожидаясь, пока он его сдаст!»

И наш библиотекарь смотрит на тебя, как на еблана, и спрашивает: «Ты в своём уме? Весь мой конёк — это давать людям согласованные и завершённые снимки данных. А ты просишь меня сломать всю мою систему учета и начать рыться в мусорной корзине с черновиками, которые, возможно, даже в итоге в архив не попадут. Да пошёл ты нахуй!»

Вот именно поэтому в постгресе, если ты выставишь READ UNCOMMITTED, он тебе просто плюнет в лицо и будет вести себя ровно как READ COMMITTED. Потому что реализовывать настоящие «грязные чтения» в MVCC — это всё равно что на гоночный болид «Формулы-1» прикрутить сани для езды по говну. Технологии разные, принципы противоположные.

Смотри на коде, чтобы совсем пиздец стало понятно:

-- Петька начал делать хуйню (Транзакция 1)
BEGIN;
UPDATE users SET balance = 1000 WHERE id = 1; -- Написал в своём черновике "balance = 1000"

-- А ты, параллельно (Транзакция 2), с уровнем READ UNCOMMITTED
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
SELECT balance FROM users WHERE id = 1; -- Чего ждёшь-то?

В системе с блокировками (типа старого доброго SQL Server) ты бы тут увидел эту самую 1000, потому что он тупо не ставит блокировок на чтение. Грязно, быстро, и можно обосраться.

Но в MVCC-движке твой SELECT упрётся в снимок данных на момент своего начала. А в этом снимке есть только то, что уже закоммичено. Версия от Петьки — незакоммиченная, она в другом, параллельном измерении. Чтобы её тебе показать, движку пришлось бы выебать себе весь мозг, начать отслеживать эти «чёрные» версии и показывать их всем желающим, похерив главный принцип — целостность снимка.

Вывод, блядь: READ UNCOMMITTED и MVCC — это как огонь и вода. Одно существует, чтобы показывать незавершённое говно, другое — чтобы его прятать. Поэтому в адекватных MVCC-базах этот уровень либо не реализован, либо является пустышкой. Хочешь грязных чтений — иди в мир блокировок, там тебя ждут и приключения с дедлоками.