предисловие
В настоящее время MySQL является одной из популярных баз данных с открытым исходным кодом. Крупные компании используют MySQL в качестве своей собственной реляционной базы данных, но в качестве базы данных базовое использование MySQL очень просто, если вы можете создавать небольшие табличные операторы (вы можно использовать инструменты для создания таблицы), небольшой запрос может использовать MySQL для хранения данных.
Такого рода бездушная операция может быть уже привычной для многих новичков. Но для некоторых людей, уже имеющих опыт разработки, этого далеко не достаточно. Вы должны изучить много знаний, связанных с базами данных, и эта статья предназначена для тщательного анализа того, как реализована MVCC в MySQL. Прочитав эту статью, вы сможете узнать, какова роль MVCC при различных уровнях изоляции? Когда будет использоваться MVCC? как пользоваться?
Пример таблицы
CREATE TABLE `test`.`Untitled` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`phone` char(11) NOT NULL,
`name` varchar(255) NOT NULL,
`age` int(3) NOT NULL,
`country` varchar(255) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_phone`(`phone`) USING BTREE,
INDEX `idx_name`(`name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
insert into person values (null, '1351111111', 'any', 20, '蜀');
insert into person values (null, '1351111112', 'bat', 21, '吴');
id | phone | name | age | country |
---|---|---|---|---|
1 | 1351111111 | any | 20 | Шу |
2 | 1351111112 | bat | 21 | Ву |
MVCC
MVCC — это реализация работы без блокировки.Без блокировки означает отсутствие блокировки. Безблокировка может значительно улучшить параллелизм базы данных. Его самая основная форма — согласованное чтение без блокировки, которое реализуется через MVCC (управление параллелизмом нескольких версий). MVCC в основном полагается на чтение для достижения.
Каждая запись в базе данных фактически имеет три скрытых столбца:
-
DB_TRX_ID
: этот столбец представляет идентификатор транзакции для этой записи. -
DB_ROLL_PTR
: этот столбец представляет собой указатель на сегмент отката, который на самом деле представляет собой цепочку версий, указывающую на запись. -
DB_ROW_ID
: идентификатор записи, если указан первичный ключ, то значение является первичным ключом. Если первичный ключ отсутствует, используется первый определенный уникальный индекс. Если уникального индекса нет, то значение генерируется по умолчанию.
start transaction;
update person set age = 22 where id = 1;
update person set name = 'out' where id = 1;
commit;
После выполнения двух вышеуказанных операторов, но до того, как транзакция будет зафиксирована, цепочка ее версий показана на следующем рисунке:
Представление чтения используется для определения того, какую запись в цепочке версий может прочитать каждый оператор чтения. Поэтому перед чтением будет создано представление для чтения. Затем прочитайте запись в соответствии с созданным представлением чтения.
В транзакции идентификатор транзакции назначается только при выполнении операций вставки, обновления и удаления. Если транзакция является транзакцией чистого чтения, то ее идентификатор транзакции по умолчанию равен 0.
Структура режима чтения выглядит следующим образом:
-
rw_trx_ids
: указывает массив текущих активных транзакций чтения и записи при создании представления чтения. -
min_trx_id
: Указывает, что при создании представления для чтения текущий отправленный номер транзакции + 1, т. е. вrw_trx_ids
Наименьшее количество транзакций в . -
max_trx_id
: Указывает текущий номер транзакции + 1 при создании представления чтения, то есть номер транзакции, который будет назначен следующей транзакции. -
curr_trx_id
: идентификатор текущей транзакции, которая создала представление для чтения.
-
trx_id < min_trx_id
, то запись находится в текущей транзакциивидимый, так как транзакция, изменившая запись версии, была зафиксирована до того, как текущая транзакция создала представление для чтения. -
trx_id in (rw_trx_ids)
, то запись находится в текущей транзакцииНевидимый, потому что транзакция, которая должна изменить запись версии, не была зафиксирована до того, как текущая транзакция сгенерирует представление для чтения. -
trx_id > max_trx_id
, то запись находится в текущей транзакцииНевидимый, так как транзакция, изменяющая запись версии, не открывается до того, как текущая транзакция создаст представление для чтения. -
trx_id = curr_trx_id
, то запись находится в текущей транзакциивидимый, потому что транзакция, изменяющая запись версии, является текущей транзакцией.
Например:
Сначала мы открыли транзакцию чтения на шаге 1. Поскольку это транзакция только для чтения, ее идентификатор транзакции равен 0 (далее именуемый транзакцией 0). Затем мы запрашиваем запись с идентификатором 1 в транзакции 0. На данный момент цепочка версий выглядит следующим образом:
Примечание. Записи, связанные с красным заголовком, находятся в дереве B+, а все записи, связанные указателем roll_ptr, находятся в журнале отмены. Все цепочки версий ниже имеют эту форму.
READ UNCOMMITTED
Этот уровень изоляции не использует MVCC. он просто выполняетselect
, то он получит последнюю запись в дереве B+. Независимо от того, была ли зафиксирована записанная транзакция.
READ COMMITTED
На уровне изоляции READ COMMITTED используется MVCC. После запуска транзакции чтения будетselect
Перед операцией создается представление для чтения.
потому что на шаге 2select
При чтении активных транзакций нет, значит, все транзакции были зафиксированы. Таким образом, он может прочитать первую запись.
Выполните шаг 3, чтобы открыть новую транзакцию с идентификатором транзакции 101 (следующее обнаруживает транзакцию 101).
Выполните шаг 4, который изменяет запись с идентификатором 1, и цепочка версий станет следующей:
Шаг 5 выполняется, транзакция 0 выполняетselect
операция, транзакция 0 создаст представление для чтения. Значения Read View следующие:
Мы следуем правилам видимости записи в цепочке версий выше:
- Первая запись в цепочке версий, ее
trx_id
не меньше чемmin_trx_id
, поэтому запись не видна. - Вторая запись в цепочке версий, ее
trx_id
меньше, чемmin_trx_id
, поэтому запись видна.
Таким образом, для этого запроса записи, которые он может получить, следующие:
Транзакция 101 выполняет операцию обновления на шаге 5, выполняет шаг 6 и фиксирует транзакцию, цепочка версий выглядит следующим образом:
Выполняем шаг 7, мы делаем это один раз в транзакции 0select
Query, так как наш уровень изоляции READ COMMITTED, этот запрос также создаст представление для чтения. Значения этого Read View следующие:
Затем в соответствии с правилами видимости цепочки версий:
- Поскольку активных транзакций нет, видно, что все транзакции были зафиксированы, поэтому
rw_trx_ids
Пусто. - Первая запись цепочки версий, ее
trx_id
меньше, чемmin_trx_id
, поэтому эта запись видна.
Тогда записи, которые можно получить по этому запросу, следующие:
После того, как транзакция 0 завершает выполнение запроса, мы открываем новую транзакцию с идентификатором транзакции 102 (далее — транзакция 102), которая также обновляет запись с идентификатором 1.
Запрос на шаге 9 анализирует сам себя. Мы напрямую даем окончательную цепочку версий двух операторов обновления, выполняемых транзакцией 102:
Выполните шаг 11, в соответствии с правилами видимости цепочки версий записи, которые он может получить:
REPEATABLE READ
На самом деле разница между REPEATABLE READ и READ COMMITTED заключается только во времени генерации Read View.
READ COMMITTED выполняется каждый разselect
При работе будет создан новый вид чтения. в то время как REPEATABLE READ будет выполняться только в первый разselect
Представление для чтения создается во время операции, пока транзакция не будет зафиксирована, всеselect
Операция заключается в использовании представления чтения, созданного в первый раз.
Повторим действия в таблице.
Сначала переходим к шагу 2, после транзакции 0 запускаем транзакцию и выполняем ее один разselect
Запрос. Создается представление для чтения. Представление чтения устроено следующим образом:
Результирующее представление для чтения будет использоваться до тех пор, пока транзакция 0 не будет зафиксирована.
Таким образом, несмотря на то, что последние две транзакции открываются, а записи изменяются, итоговая цепочка версий становится следующей:
Но транзакция 0 по-прежнему может читать только первую запись, а именно:
Независимо от того, когда выполняется транзакция 0select * from person where id = 1;
Прочитать записи, тогда он будет использовать только представление чтения, созданное в первый раз, для выбора записей, которые можно прочитать в цепочке версий.
SERIALIZABLE
Этот уровень изоляции не использует MVCC. Если использовать обычныйselect
оператор, он добавляет операторlock in share mode
, что становится согласованным чтением блокировки. Предположим, транзакция читает запись, а другие транзакции блокируются при изменении этой записи. Если предположить, что транзакция изменяет запись, прочтение записи другими транзакциями будет заблокировано.
На этом уровне изоляции операции чтения и записи становятся последовательными операциями.
Суммировать
Из приведенной выше статьи мы можем узнать, что MVCC используется только на уровнях изоляции READ COMMITTED и REPEATABLE READ.
Но READ COMMITTED и REPEATABLE READ используют MVCC по-разному:
- READ COMMITTED выполняется каждый раз
select
Каждая операция будет генерировать представление для чтения. - REPEATABLE READ выполняется только в первый раз
select
Read View будет генерироваться только во время работы, а последующиеselect
Операции будут использовать первое сгенерированное представление чтения.
Уровни изоляции READ UNCOMMITTED и SERIALIZABLE не используют MVCC.
Их операции чтения также отличаются:
- READ UNCOMMITTED при каждом выполнении
select
Буду читать последние записи. - SERIALIZABLE выполняется каждый раз
select
операции будут сопровождаться операторомlock in share mode
,сделатьselect
Становится согласованным блокирующим чтением, сериализующим чтением и записью.