Вода MVCC немного глубокая, но это действительно круто для понимания!

Java задняя часть MySQL
Вода MVCC немного глубокая, но это действительно круто для понимания!

@[toc] Я написал статью ранее и поделился с вами проблемой записей таблицы запросов в MySQL, которая включает контроль многоверсионного параллелизма точки знаний MVCC. Я не понимаю этой проблемы, я всегда чувствую, что чего-то не хватает. Итак, сегодня я хочу воспользоваться моментом, чтобы поговорить с вами о MVCC.

Чтобы понять MVCC, лучше всего сначала понять уровень изоляции транзакций в InnoDB, иначе сложно понять MVCC, просто взглянув на концепцию.

1. Уровень изоляции

1.1 Теория

В MySQL существует четыре уровня изоляции транзакций:

  • Сериализация (SERIALIZABLE)
  • ПОВТОРЯЕМОЕ ЧТЕНИЕ
  • ПРОЧИТАТЬ СОВЕРШЕНО
  • ЧИТАТЬ БЕЗ ЗАЯВЛЕНИЙ

Четыре различных уровня изоляции имеют следующие значения:

  1. SERIALIZABLE

Если уровень изоляции сериализован, текущие транзакции выполняются последовательно между пользователями друг за другом, и этот уровень изоляции обеспечивает максимальную изоляцию между транзакциями.

  1. REPEATABLE READ

При повторяющемся чтении на этом уровне изоляции транзакция не рассматривается как последовательность. Однако изменения выполняемой в данный момент транзакции по-прежнему не видны внешнему миру, то есть, если пользователь выполняет один и тот же оператор SELECT несколько раз в другой транзакции, результат всегда один и тот же. (Поскольку изменения данных, произведенные выполняющейся транзакцией, не видны извне).

  1. READ COMMITTED

Уровень изоляции READ COMMITTED менее безопасен, чем уровень изоляции REPEATABLE READ. Транзакции на уровне READ COMMITTED могут видеть изменения данных другими транзакциями. То есть во время обработки транзакции несколько операторов SELECT из одной и той же транзакции могут возвращать разные результаты, если другие транзакции изменили соответствующую таблицу.

  1. READ UNCOMMITTED

READ UNCOMMITTED обеспечивает минимальную изоляцию между транзакциями. В дополнение к легкому созданию фантомных операций чтения и неповторяемых операций чтения, транзакции на этом уровне изоляции могут считывать данные, которые еще не зафиксированы другими транзакциями.Зафиксированные изменения отменяются их родительской транзакцией, что приводит к большому количеству изменений данных.

В базах данных MySQL уровень изоляции транзакций по умолчанию — REPEATABLE READ.

1.2 Практика SQL

Затем приведенная выше теория проверяется читателем с помощью нескольких простых SQL-запросов.

1.2.1 Просмотр уровня изоляции

Вы можете просмотреть глобальный уровень изоляции по умолчанию для экземпляра базы данных и уровень изоляции текущего сеанса с помощью следующего SQL:

До MySQL 8 используйте следующую команду для просмотра уровня изоляции MySQL:

SELECT @@GLOBAL.tx_isolation, @@tx_isolation;

Результат запроса показан на рисунке:

Как видите, уровень изоляции по умолчанию — REPEATABLE-READ, как глобальный уровень изоляции, так и уровень изоляции текущего сеанса.

Начиная с MySQL8, проверьте уровень изоляции MySQL по умолчанию с помощью следующей команды:

SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;

Просто ключевое слово поменялось, все остальное то же самое.

Уровень изоляции можно изменить с помощью следующих команд (разработчикам рекомендуется изменять текущий уровень изоляции сеанса при изменении, не изменяя глобальный уровень изоляции):

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

Приведенный выше SQL означает, что для уровня изоляции базы данных текущего сеанса необходимо установить значение READ UNCOMMITTED.После того, как настройка выполнена успешно, запросите уровень изоляции еще раз и обнаружите, что уровень изоляции текущего сеанса изменился, как показано на рисунке 1-2:

Обратите внимание, что если изменяется только уровень изоляции текущего сеанса, после изменения сеанса уровень изоляции будет восстановлен до уровня изоляции по умолчанию, поэтому при тестировании мы можем изменить уровень изоляции текущего сеанса.

1.2.2 READ UNCOMMITTED

1.2.2.1 Подготовка тестовых данных

READ UNCOMMITTED — это самый низкий уровень изоляции, который существует вГрязные чтения, неповторяемые чтения и фантомные чтенияПроблема, поэтому здесь мы сначала рассмотрим этот уровень изоляции, чтобы каждый мог понять, в чем заключаются три проблемы.

Они представлены отдельно ниже.

Сначала создайте простую таблицу, предустановив два данных, как показано ниже:

Данные в таблице очень простые: есть два пользователя, javaboy и itboyhub, и на их счетах у каждого по 1000 юаней. Теперь смоделируйте операцию передачи между этими двумя пользователями.

Обратите внимание, что если считыватель использует Navicat, разные окна запросов соответствуют разным сеансам.Если считыватель использует SQLyog, разные окна запросов соответствуют одному и тому же сеансу, поэтому, если вы используете SQLyog, считывателю необходимо открыть новое соединение. новое соединение.

1.2.2.2 Грязные чтения

Когда транзакция считывает данные, которые не были зафиксированы другой транзакцией, это называется грязным чтением. Конкретные операции заключаются в следующем:

  1. Сначала откройте два окна операций SQL, предполагая, что A и B соответственно, введите следующий SQL в окне A (не выполнять после завершения ввода):
START TRANSACTION;
UPDATE account set balance=balance+100 where name='javaboy';
UPDATE account set balance=balance-100 where name='itboyhub';
COMMIT;
  1. Выполните следующий SQL-запрос в окне B и измените уровень изоляции транзакций по умолчанию на READ UNCOMMITTED следующим образом:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
  1. Затем введите следующий SQL в окно B. После завершения ввода сначала выполните первую строку, чтобы открыть транзакцию (обратите внимание, что должна быть выполнена только одна строка):
START TRANSACTION;
SELECT * from account;
COMMIT;
  1. Далее выполните первые два SQL в окне A, то есть откройте транзакцию и добавьте 100 юаней на счет javaboy.

  2. Войдите в окно B и выполните второй запрос SQL (SELECT * from user;) в окне B. Результаты следующие:

Видно, что хотя транзакция в окне A не была отправлена, соответствующие изменения данных можно запросить в окне B.

Этогрязное чтениепроблема.

1.2.2.3 Неповторяющееся чтение

Неповторяющееся чтение означает, что транзакция последовательно считывает одну и ту же запись, но два раза считываются разные данные, что называется неповторяемым чтением. Конкретные шаги операции следующие (перед операцией восстановите деньги на обоих счетах до 1000):

  1. Сначала откройте два окна запросов A и B и установите для уровня изоляции транзакций базы данных B значение READ UNCOMMITTED. Для конкретного SQL см. выше, и здесь мы не будем вдаваться в подробности.

  2. Введите следующий SQL в окно B, а затем выполните только первые два SQL, чтобы открыть транзакцию и запросить учетную запись javaboy:

START TRANSACTION;
SELECT * from account where name='javaboy';
COMMIT;

Первые два результата выполнения SQL следующие:

  1. Выполните следующий SQL-запрос в окне A, чтобы добавить 100 юаней к учетной записи javaboy, следующим образом:
START TRANSACTION;
UPDATE account set balance=balance+100 where name='javaboy';
COMMIT;

4. Снова вернитесь в окно B и выполните второй SQL-запрос окна B, чтобы просмотреть учетную запись javaboy. Результаты следующие:

Учетная запись javaboy изменилась, то есть учетная запись javaboy проверяется дважды до и после, и результаты противоречивы.неповторяемое чтение.

Отличие от грязного чтения заключается в том, что грязное чтение видит данные, которые не были зафиксированы другими транзакциями, а неповторяющееся чтение видит данные, которые были зафиксированы другими транзакциями (поскольку текущий SQL также находится в транзакции, возможно, что вы не хочу видеть другие транзакции.данные уже отправлены).

1.2.2.4 Фантомное чтение

Фантазмальное чтение очень похоже на неповторяемое чтение, а видение имен — это галлюцинации.

Я привожу простой пример.

Введите следующий SQL в окне A:

START TRANSACTION;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;

Затем введите следующий SQL в окне B:

START TRANSACTION;
SELECT * from account;
delete from account where name='zhangsan';
COMMIT;

Выполняем следующие шаги:

  1. Сначала выполните первые две строки окна B, запустите транзакцию и одновременно запросите данные в базе данных.В настоящее время запрашиваются только данные javaboy и itboyhub.
  2. Выполните первые две строки окна A, добавьте пользователя с именем zhangsan в базу данных, будьте осторожны, чтобы не зафиксировать транзакцию.
  3. Выполните вторую строку окна B. Из-за проблемы с грязным чтением пользователь zhangsan может быть опрошен в это время.
  4. Выполните третью строку окна B, чтобы удалить запись с именем zhangsan.В это время возникнет проблема с удалением.Хотя zhangsan можно запросить в окне B, эта запись не была отправлена, потому что она читается из-за грязное чтение. Оно там, поэтому его нельзя удалить. В этот момент возникла галлюцинация, и был чжансан, но его нельзя было удалить.

Этогаллюцинации.

Прочитав приведенный выше случай, каждый должен понятьгрязное чтение,неповторяемое чтениеа такжегаллюцинацииЧто означает каждый.

1.2.3 READ COMMITTED

По сравнению с READ UNCOMMITTED, READ COMMITTED в основном решает проблему грязных чтений, но не неповторяющихся чтений и фантомных чтений.

Измените уровень изоляции транзакции наREAD COMMITTEDПосле этого повторите приведенный выше тест для случая грязного чтения и убедитесь, что проблемы с грязным чтением нет; повторите вышеуказанный тест для случая неповторяемого чтения и обнаружите, что проблема неповторяемого чтения все еще существует.

Приведенный выше случай не относится к тесту фантомного чтения, давайте изменим тестовый пример фантомного чтения.

Или два окна A и B, измените уровень изоляции окна B наREAD COMMITTED,

Затем введите следующий тестовый SQL в окно A:

START TRANSACTION;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;

Введите следующий тестовый SQL в окно B:

START TRANSACTION;
SELECT * from account;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;

Метод испытания следующий:

  1. Сначала выполните первые две строки SQL в окне B, откройте транзакцию и запросите данные.На данный момент найдены только два пользователя, javaboy и itboyhub.
  2. Выполните первые две строки SQL в окне A, вставив запись, но не зафиксировав транзакцию.
  3. Выполните вторую строку SQL в окне B. Поскольку проблема грязного чтения отсутствует, данные, добавленные в окно A, в настоящее время не могут быть найдены.
  4. Выполните третью строку SQL в окне B. Поскольку поле имени уникально, его нельзя вставить сюда. В этот момент возникает галлюцинация.Очевидно, что пользователя zhangsan нет, но zhangsan не может быть вставлен.

1.2.4 REPEATABLE READ

По сравнению с READ COMMITTED, REPEATABLE READ еще больше решает проблему неповторяющихся чтений, но не фантомных чтений.

Тест на фантомное чтение в REPEATABLE READ в основном такой же, как и в предыдущем разделе, разница в том, что не забудьте зафиксировать транзакцию после выполнения вставки SQL на втором шаге.

Поскольку ПОВТОРЯЕМОЕ ЧТЕНИЕ разрешило неповторяющееся чтение, даже если транзакция зафиксирована на втором этапе, зафиксированные данные не могут быть найдены на третьем этапе, и произойдет ошибка, когда четвертый шаг продолжит вставку.

Обратите внимание, что REPEATABLE READ также является уровнем изоляции транзакций базы данных по умолчанию для механизма InnoDB.

1.2.5 SERIALIZABLE

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

Если для текущего уровня изоляции транзакций установлено значение SERIALIZABLE, то при открытии других транзакций в это время он будет блокироваться, а другие транзакции должны ждать, пока текущая транзакция не будет зафиксирована, прежде чем другие транзакции смогут быть успешно открыты, поэтому предыдущие проблемы грязного чтений, неповторяемых чтений и фантомных чтений здесь нет.

1.3 Резюме

В целом соответствие между уровнями изоляции и грязными чтениями, неповторяемыми чтениями и фантомными чтениями выглядит следующим образом:

уровень изоляции грязное чтение неповторяемое чтение фантомное чтение
READ UNCOMMITTED позволять позволять позволять
READ COMMITED не допускается позволять позволять
REPEATABLE READ не допускается не допускается позволять
SERIALIZABLE не допускается не допускается не допускается

Зависимость производительности показана на рисунке:

Сонг Гэ также недавно записал видео об уровне изоляции, вы можете обратиться к следующему:

2. Чтение моментального снимка и текущее чтение

Далее нам нужно выяснить проблему: чтение моментального снимка и текущее чтение.

2.1 Чтение снимка

SnapShot Read — это согласованное и разблокированное чтение, что является одной из основных причин высокого параллелизма механизма хранения InnoDB.

При уровне изоляции повторяемого чтения при запуске транзакции будет сделана фотография (моментальный снимок) текущей библиотеки.Данные, считанные при чтении моментального снимка, — это либо данные, когда была сделана фотография, либо данные, вставленные/измененные сама текущая транзакция data.

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

2.2 Текущие показания

Текущее чтение соответствует чтению моментального снимка. Текущее чтение предназначено для чтения последних данных, а не данных исторической версии. Другими словами, при уровне изоляции повторяемого чтения, если используется текущее чтение, оно также может читать что были совершены другие транзакции.

Сун Гэ приводит пример:

Транзакция MySQL открывает два сеанса A и B.

Сначала запустите транзакцию в сеансе A и запросите запись с идентификатором 1:

Затем мы изменяем данные с идентификатором 1 в сеансе B следующим образом:

Обратите внимание, что сеанс B не должен открывать транзакцию или открывать транзакцию своевременной фиксации, иначе оператор обновления займет эксклюзивную блокировку, что приведет к блокировке, когда блокировка используется в сеансе A в течение некоторого времени.

Затем вернитесь к сеансу A, чтобы продолжить операцию запроса, как показано ниже:

Видно, что первый запрос в сеансе A — это чтение моментального снимка, которое считывает состояние данных при открытии текущей транзакции, а последние два запроса — это текущее чтение, которое считывает текущие последние данные (после модификации в Сессия B Данные).

3. undo log

Давайте немного узнаем о журнале отмен, который также помогает нам понять следующий MVCC, здесь мы кратко представим его.

Мы знаем, что транзакция базы данных имеет возможность отката.Так как ее можно откатить, старые данные должны быть записаны до того, как данные будут изменены, как основа для будущего отката, то эта запись является журналом отмены.

Когда мы хотим добавить запись, записываем идентификатор добавленных данных в журнал отмены и соответственно удаляем данные при откате в будущем; когда мы хотим удалить или изменить данные, записываем исходные данные в журнал отмены и восстанавливаем данные соответственно в будущем. Поскольку операция запроса не включает операцию отката, ее не нужно записывать в журнал отмены.

4. Формат строки

Далее рассмотрим формат строки, который также помогает нам понять MVCC.

Формат строки — это формат, в котором InnoDB сохраняет данные каждой строки при сохранении данных каждой строки.

В базе данных существует несколько форматов строк, таких как COMPACT, REDUNDANT, DYNAMIC, COMPRESSED и т. д. Однако независимо от того, какой формат строки используется, нельзя избежать следующих скрытых столбцов данных:

Столбец 1, столбец 2, столбец 3 на приведенном выше рисунке, вплоть до столбца N, являются столбцами таблицы в нашей базе данных, в которых хранятся наши обычные данные.В дополнение к этим столбцам, которые сохраняют данные, есть три дополнительных столбца. данных добавлено. Это то, на чем мы собираемся сосредоточиться здесьDB_ROW_ID,DB_TRX_ID,DB_ROLL_PTRТри столбца:

  • DB_ROW_ID: этот столбец занимает 6 байт и представляет собой идентификатор строки, используемый для уникальной идентификации строки данных. Если пользователь не задал первичный ключ при создании таблицы, система создаст индекс первичного ключа на основе этого столбца.
  • DB_TRX_ID: этот столбец занимает 6 байт и является идентификатором транзакции. В механизме хранения InnoDB, когда мы хотим открыть транзакцию, мы будем запрашивать идентификатор транзакции в системе транзакций InnoDB, и этот идентификатор транзакции являетсяСтрого возрастающее и уникальное числотекущая строка данных изменена какой транзакцией, соответствующий идентификатор транзакции будет записан в текущей строке.
  • DB_ROLL_PTR: Этот столбец занимает 7 байт и является указателем отката. Этот указатель отката указывает на адрес журнала журнала отмен. Через этот журнал журнала отмен запись может быть восстановлена ​​до предыдущей версии.

Хорошо, вот немного о формате строки данных.

5. MVCC

Получив предварительные знания из предыдущего раздела, давайте формально рассмотрим MVCC.

MVCC, полное название на английском языке — Multi-Version Concurrency Control, переводится на китайский язык как Multi-Version Concurrency Control.

Основная идея MVCC состоит в том, чтобы сохранить историческую версию строки данных и реализовать контроль параллелизма базы данных за счет управления несколькими версиями строки данных.

Проще говоря, когда записи, которые мы обычно видим, хранятся в базе данных, может быть не одна запись, а несколько исторических версий.

Как показано ниже:

Эта картина хорошо понятна, а MVCC, думаю, не очень понятна всем.

Далее я расскажу вам об этой картине в сочетании с разными уровнями изоляции.

5.1 REPEATABLE READ

Во-первых, когда мы оперируем строкой данных через INSERT\DELETE\UPDATE, будет сгенерирован идентификатор транзакции, и этот идентификатор транзакции также будет храниться в записи строки (DB_TRX_ID), то есть какая транзакция изменяет текущую строку данных. После этого записывается.

Операции INSERT\DELETE\UPDATE будут генерировать соответствующий журнал отмены операций, каждая строка записей имеетDB_ROLL_PTRУкажите журнал журнала отмены, каждую строку записей, выполнив журнал журнала отмены, вы можете восстановить предыдущую запись, предыдущую запись, предыдущую предыдущую запись...

Когда мы открываем транзакцию, мы сначала будем запрашивать идентификатор транзакции в системе транзакций InnoDB.Этот идентификатор является строго возрастающим числом.В момент, когда текущая транзакция будет открыта, система создаст массив, и массив сохранит все идентификатор текущей активной транзакции, так называемая активная транзакция относится к транзакции, которая была открыта, но еще не зафиксирована.

Минимальное значение в этом массиве понять несложно, некоторые друзья могут ошибочно подумать, что максимальное значение в массиве — это id текущей транзакции, на самом деле это не обязательно, и оно может быть больше. Поскольку от приложения к trx_id до создания массива требуется время, другие сеансы также могут обращаться к trx_id в течение этого периода.

Когда текущая транзакция хочет просмотреть строку данных, она сначала просмотрит строку данных.DB_TRX_ID:

  1. Если это значение равно текущему идентификатору транзакции, это означает, что оно изменено текущей транзакцией, и данные видны.
  2. Если это значение меньше минимального значения в массиве, это означает, что когда мы запускаем текущую транзакцию, транзакция, участвующая в модификации данных этой строки, была зафиксирована, и текущая строка данных видна.
  3. Если это значение больше, чем максимальное значение в массиве, это означает, что эта строка данных после того, как мы открыли транзакцию, но перед фиксацией другой сеанс также открывает транзакцию и изменяет эту строку данных, тогда эта строка данных Невидимый.
  4. Если размер этого значения находится между максимальным и минимальным значениями в массиве (замкнутый диапазон), а значение отсутствует в массиве, значит, это тоже данные, измененные зафиксированной транзакцией, которые видны.
  5. Если размер этого значения находится между максимальным значением и минимальным значением в массиве (замкнутый диапазон), а значение находится в массиве (не равно текущему идентификатору транзакции), это означает, что это данные, измененные незафиксированная транзакция и не видна.

Следует хорошо понимать первые три ситуации, особенно две последние.Сун Гэ приводит простой пример.

Например, у нас есть четыре сеанса A, B, C и D. Сначала транзакцию запускают соответственно A, B и C. Идентификаторы транзакции 3, 4 и 5. Затем сеанс C фиксирует транзакцию, но A а Б нет. Затем сеанс D также запускает транзакцию, и идентификатор транзакции равен 6, затем, когда сеанс D начинает транзакцию, значение в массиве равно [3,4,6]. Теперь предположим, что есть ряд данныхDB_TRX_IDравно 5 (четвертый случай), то строка данных видна (поскольку она была зафиксирована при открытии текущей транзакции); если есть строка данныхDB_TRX_IDравно 4, то строка не видна (поскольку она не была зафиксирована).

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

Позвольте мне привести пример, предполагая следующую таблицу:

Теперь есть две сессии A и B, сначала запускаем транзакцию в A:

Затем выполните операцию модификации в сеансе B (без явного открытия транзакции, транзакция будет открыта внутри SQL обновления, и транзакция будет автоматически зафиксирована после завершения обновления):

Затем вернитесь к сеансу A, запросите запись и обнаружите, что значение не изменилось, что соответствует ожиданиям (в настоящее время уровень изоляции — повторяемое чтение), а затем выполните операцию модификации в A, а затем выполните запрос после модификация завершена, как показано на следующем рисунке:

Видно, что обновление на самом деле обновляется на основе 100. Это тоже понятно.Если обновлять на основе 99, то обновление 100 потеряется, что явно неправильно.

На самом деле обновление в MySQL это сначала чтение, а потом обновление.При чтении по умолчанию стоит текущее чтение, то есть оно будет заблокировано. Таким образом, в приведенном выше случае, если транзакция явно открыта в сеансе B и нет незафиксированных, то оператор обновления в сеансе A будет заблокирован.

Это MVCC, и существует несколько версий одной записи. Реализует контроль параллелизма чтения-записи, при этом операции чтения и записи не блокируют друг друга, в то же время в MVCC принята оптимистическая блокировка, чтение данных не блокируется, а запись данных блокирует только строки, что снижает вероятность взаимоблокировки; и чтение моментальных снимков также может быть реализовано соответствующим образом.

5.2 READ COMMITTED

READ COMMITTED похож на REPEATABLE READ, основное отличие состоит в том, что последний создает согласованное представление (создавая массив, в котором перечислены идентификаторы активных транзакций) в начале каждой транзакции, а первый пересчитывает новое представление перед выполнением каждого оператора.

Таким образом, уровень изоляции READ COMMITTED будет видеть данные, зафиксированные другими сеансами (даже если другой сеанс открыт позже текущего сеанса).

6. Резюме

MVCC в определенной степени обеспечивает параллелизм чтения и записи, но он действителен только при двух уровнях изоляции READ COMMITTED и REPEATABLE READ.

В то время как READ UNCOMMITTED всегда считывает последнюю строку данных, SERIALIZABLE блокирует все прочитанные строки, обе из которых несовместимы с MVCC.

Хорошо, я не знаю, понимаете ли вы, ребята. Если у вас есть какие-либо вопросы, пожалуйста, оставьте сообщение для обсуждения.