Концепция мультиверсионности (MVCC, Multi-Version Concurrency Control) возникла как альтернатива строгим блокировкам, чтобы обеспечить параллельную работу большого числа транзакций. Это было важно для снижения конфликтов и блокировок при одновременном доступе к данным, что особенно критично в OLTP-системах.
Традиционные подходы к блокировке (например, row-level locking) могут приводить к тормозам приложений при высокой конкуренции. Задача MVCC — позволить транзакциям читать согласованные снимки данных, даже если параллельно выполняются операции записи, тем самым обеспечивая изоляцию и одновременный доступ.
MVCC реализуется в популярных СУБД (PostgreSQL, Oracle, MySQL/InnoDB) с помощью хранения историй версий строк. При считывании каждая транзакция видит только те строки, которые были зафиксированы до её старта, а вставки/обновления создают новые версии строк без их немедленного удаления.
Пример запроса (PostgreSQL):
BEGIN TRANSACTION; SELECT * FROM orders WHERE status = 'processing'; UPDATE orders SET status = 'completed' WHERE id = 42; COMMIT;
Пока транзакция не завершена — остальные пользователи будут видеть предыдущую версию строки, и только после коммита изменения станут доступны новым транзакциям.
Ключевые особенности:
Может ли MVCC полностью избавить от всех видов блокировок и конфликтов?
Нет, в MVCC всё равно возможны конфликты при одновременном обновлении одних и тех же строк — например, при одновременных UPDATE возникает конфликт коммитов (write-write conflict), и СУБД выбрасывает ошибку или откатывает одну из транзакций.
Когда старые версии строк удаляются в MVCC и может ли это привести к утечкам памяти?
В большинстве СУБД старые версии строк удаляются специальными процессами (VACUUM в PostgreSQL). Если не запускать эти процессы, база "раздувается" и производительность падает.
Работают ли "select for update" корректно в условиях MVCC, и почему необходим локинг?
Да, запросы SELECT FOR UPDATE блокируют строки для исключения конфликтов при параллельных изменениях, иначе бы могли возникнуть "lost update".
Пример:
BEGIN; SELECT * FROM products WHERE id = 123 FOR UPDATE; UPDATE products SET quantity = quantity - 1 WHERE id = 123; COMMIT;
В крупном интернет-магазине была реализована схема с частыми UPDATE заказов без настройки VACUUM. Через месяц база выросла в 10 раз, запросы замедлились в разы.
Плюсы:
Минусы:
Реализован регулярный autovacuum, использованы контроль write-conflict, изоляция на уровне REPEATABLE READ только для критичных запросов.
Плюсы:
Минусы: