Большинство транзакционных подсистем хранения в MySQL не используют простой механизм построчной блокировки. Они применяют построчную блокировку в сочетании с методикой увеличения конкурентности, известной как управление конкурентным доступом с помощью многоверсионности (multiversion concurrency control, MVCC). Нельзя сказать, что методика MVCC присуща исключительно MySQL — она используется также в Oracle, PostgreSQL и некоторых других СУБД, хотя из-за отсутствия стандарта возможны существенные различия в их работе.
Вы можете воспринимать MVCC как вариацию построчной блокировки. Во многих случаях оно позволяет обойтись без блокировки и существенно снизить издержки. В зависимости от способа реализации MVCC может допускать чтение без блокировки, блокируя только необходимые строки во время операций записи.
MVCC сохраняет мгновенный снимок состояния данных в определенный момент времени. Это означает, что транзакции вне зависимости от их длительности могут видеть согласованные данные. А также что различные транзакции могут видеть разные данные в одних и тех же таблицах в одно и то же время! Если вы никогда не сталкивались с этим раньше, то можете сильно удивиться, но по мере знакомства с этой технологией все становится понятнее.
Каждая подсистема хранения реализует MVCC по-своему. В некоторых вариантах применяется оптимистическое и пессимистическое управление конкурентным доступом. Мы проиллюстрируем один из способов работы MVCC, объяснив упрощенную версию поведения InnoDB.
InnoDB реализует MVCC, сохраняя вместе с каждой строкой два скрытых значения, которые показывают, когда строка была создана и когда истек срок ее хранения (или она была удалена). Вместо хранения фактического момента времени, когда произошли данные события, строка хранит номер версии системы для этого момента. В начале каждой транзакции этот номер увеличивается на единицу. Каждая транзакция хранит собственную запись текущей версии системы на момент своего начала. Каждый запрос должен сравнивать номера версий каждой строки с версией транзакции. Посмотрим, как эта методика применяется к конкретным операциям, когда транзакция имеет уровень изоляции REPEATABLE READ.
SELECT
. InnoDB должна проверить каждую строку на соответствие двум критериям.- Найти версию строки, по крайней мере такой же старой, как версия транзакции (то есть ее номер версии должен быть меньше номера версии транзакции или равен ему). Это гарантирует, что либо строка существовала до начала транзакции, либо транзакция создала или изменила эту строку.
- Версия удаления строки должна быть не определена, или ее значение должно быть больше, чем версия транзакции. Это гарантирует, что строка не была удалена до начала транзакции.
Строки, прошедшие обе проверки, могут быть возвращены в качестве результата запроса.
INSERT
. InnoDB записывает текущий номер версии системы вместе с новой строкой.DELETE
. InnoDB записывает текущий номер версии системы как ID удаления строки.- UPDATE. InnoDB записывает новую копию строки, используя номер версии системы в качестве версии новой строки. Она также записывает номер версии системы как версию удаления старой строки.
Благодаря хранению дополнительных записей большинство запросов на чтение никогда не будут ставить блокировки. Они просто как можно быстрее считывают данные, выбирая только те строки, которые удовлетворяют заданному критерию. Недостатком такого подхода является то, что подсистема хранения должна записывать для каждой строки дополнительные данные, выполнять лишнюю работу при проверке строк и производить некоторые дополнительные служебные операции.
MVCC работает только на уровнях изолированности REPEATABLE READ и READ COMMITTED. Уровень READ UNCOMMITTED несовместим c MVCC, поскольку запросы не считывают версию строки, соответствующую их версии транзакции. Несмотря ни на что они читают самую последнюю версию. Уровень SERIALIZABLE несовместим с MVCC, поскольку операции чтения блокируют каждую возвращаемую строку.