Статья поможет вам освоить согласованное представление mysql (MVCC)

MySQL

Когда дело доходит до транзакций, вы обязательно вспомните ACID (Atomicity, Consistency, Isolation, Durability, то есть атомарность, непротиворечивость, изолированность, долговечность).

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

Уровни изоляции транзакций стандарта SQL включают в себя: незафиксированное чтение, зафиксированное чтение, повторяемое чтение и сериализуемое.

  • Read uncommitted означает, что когда транзакция не зафиксирована, ее изменения могут быть видны другим транзакциям.
  • Чтение фиксации означает, что после фиксации транзакции ее изменения будут видны другим транзакциям.
  • Повторяющееся чтение означает, что данные, видимые во время выполнения транзакции, всегда совпадают с данными, видимыми транзакцией при ее запуске. Конечно, при уровне изоляции повторяющегося чтения незафиксированные изменения также невидимы для других транзакций.
  • Сериализация, как следует из названия, предназначена для одной и той же строки записей: «запись» добавит «блокировку записи», «чтение» добавит «блокировку чтения». Когда возникает конфликт блокировки чтения-записи, транзакция, к которой был получен доступ позже, должна дождаться завершения выполнения предыдущей транзакции, прежде чем продолжить.

В MySQL поддерживается четыре уровня изоляции.

Хотя MySQL поддерживает четыре уровня изоляции, это несколько отличается от допустимых проблем на всех уровнях изоляции, указанных в стандарте SQL.При уровне изоляции REPEATABLE READ MySQL может запрещать возникновение фантомных проблем чтения.

Мы можем пройти:SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;установить уровень изоляции.

Есть 4 необязательных значения для уровня:

level: {
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE
}

Принцип MVCC

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

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

ReadView

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

ReadView в основном содержит еще 4 важных содержимого:

  • m_ids: список идентификаторов транзакций, представляющих транзакции чтения и записи, которые активны в текущей системе при создании ReadView.
  • min_trx_id: указывает наименьший идентификатор транзакции в активных транзакциях чтения и записи в текущей системе при создании ReadView, то есть наименьшее значение в m_ids.
  • max_trx_id: указывает значение идентификатора, которое должно быть присвоено следующей транзакции в системе при создании ReadView.
  • Creator_trx_id: указывает идентификатор транзакции, создавшей ReadView.

Как работает ReadView?

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

  • Если значение атрибута trx_id доступной версии совпадает со значением Creator_trx_id в ReadView, это означает, что текущая транзакция обращается к своей измененной записи, поэтому эта версия может быть доступна текущей транзакции.
  • Если значение атрибута trx_id доступной версии меньше значения min_trx_id в ReadView, это указывает на то, что транзакция, сгенерировавшая эту версию, была зафиксирована до того, как текущая транзакция сгенерирует ReadView, поэтому текущая транзакция может получить доступ к этой версии.
  • Если значение атрибута trx_id версии, к которой осуществляется доступ, больше, чем значение max_trx_id в ReadView, это указывает на то, что транзакция, создающая эту версию, открывается после того, как текущая транзакция создает ReadView, поэтому текущая транзакция не может получить доступ к этой версии.
  • Если значение атрибута trx_id запрашиваемой версии находится между min_trx_id и max_trx_id ReadView, необходимо судить о том, находится ли значение атрибута trx_id в списке m_ids. версия, сгенерированная при создании ReadView, зафиксирована, и к этой версии можно получить доступ.

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

Очень большая разница между уровнями изоляции READ COMMITTED и REPEATABLE READ в MySQL заключается в том, что ониКогда создавать ReadViewразные.

Мы используем пример здесь, чтобы объяснить:

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `k` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1) ;
Транзакция А Транзакция Б
begin
begin
update t set k= k+1 where id=1;
совершить;
update t set k = k+1 where id=1;
select k from t where id =1;
commit;

В этом примере мы делаем следующие предположения:

  1. Номера версий транзакций A и B равны 100 и 200 соответственно, и в текущей системе есть только эти 3 транзакции;
  2. До запуска трех транзакций строка trx_id этой строки данных (1,1) была равна 90.

READ COMMITTED — генерировать ReadView перед каждым чтением данных

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

# 使用READ COMMITTED隔离级别的事务
BEGIN;

# SELECT1:Transaction 100、200未提交
select k from t where id=1 ; # 得到值为1

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

  • При выполнении оператора SELECT сначала будет сгенерирован ReadView.Содержимое списка m_ids ReadView — [100, 200], min_trx_id — 100, max_trx_id — 201, а создатель_trx_id — 0.
  • Затем выберите видимые записи из цепочки версий.Последняя версия имеет значение trx_id 200, которое находится в списке m_ids, поэтому она не соответствует требованиям видимости.
  • Значение trx_id следующей версии тоже равно 100, что тоже есть в списке m_ids, поэтому не соответствует требованиям, и продолжает переходить к следующей версии.
  • Значение trx_id следующей версии равно 90, что меньше значения min_trx_id, равного 100, в ReadView, поэтому эта версия соответствует требованиям.

После этого мы фиксируем транзакцию транзакции B, а затем продолжаем поиск в транзакции, которая только что использовала уровень изоляции READ COMMITTED, следующим образом:

# 使用READ COMMITTED隔离级别的事务
BEGIN;

# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到值为1

# SELECT2:Transaction 200提交,Transaction 100未提交
SELECT * FROM hero WHERE number = 1; # 得到值为2

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

  • При выполнении оператора SELECT будет сгенерирован отдельный ReadView.Содержимое списка m_ids ReadView равно [100] (транзакция с идентификатором транзакции 200 была зафиксирована, поэтому ее не будет в момент создания снимка снова), а min_trx_id равен 100, max_trx_id равен 201, Creator_trx_id равен 0.
  • Затем выберите видимые записи из цепочки версий.Как видно из рисунка, значение trx_id последней версии равно 100, которая находится в списке m_ids, поэтому не соответствует требованиям видимости.
  • Значение trx_id следующей версии равно 200, что меньше, чем max_trx_id, и его нет в списке m_ids, поэтому оно видимо, а возвращаемое значение равно 2.

ПОВТОРЯЕМОЕ ЧТЕНИЕ — генерировать ReadView при первом чтении данныхПредположим теперь, что транзакция с уровнем изоляции REPEATABLE READ начинает выполняться:

# 使用REPEATABLE READ隔离级别的事务
BEGIN;

# SELECT1:Transaction 100、200未提交
SELECT * FROM hero WHERE number = 1; # 得到值为1

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

  • При выполнении оператора SELECT сначала будет сгенерирован ReadView.Содержимое списка m_ids ReadView — [100, 200], min_trx_id — 100, max_trx_id — 201, а создатель_trx_id — 0.
  • Затем выберите видимые записи из цепочки версий.Значение trx_id этой версии равно 100, которое находится в списке m_ids, поэтому оно не соответствует требованиям видимости.
  • В следующей версии значение trx_id этой версии равно 200, что также находится в списке m_ids, поэтому не соответствует требованиям, и продолжает переходить к следующей версии.
  • Значение trx_id следующей версии равно 90, что меньше значения min_trx_id, равного 100, в ReadView, поэтому эта версия соответствует требованиям.

После этого мы отправляем транзакцию транзакции B Затем перейдите к транзакции, которая только что использовала уровень изоляции REPEATABLE READ, чтобы продолжить поиск:

# 使用REPEATABLE READ隔离级别的事务
BEGIN;

# SELECT1:Transaction 100、200均未提交
SELECT * FROM hero WHERE number = 1; # 得到值为1

# SELECT2:Transaction 200提交,Transaction 100未提交
SELECT * FROM hero WHERE number = 1; # 得到值为1

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

  • Поскольку уровень изоляции текущей транзакции REPEATABLE READ, а ReadView был сгенерирован до выполнения SELECT1, в это время непосредственно повторно используется предыдущий ReadView.Содержимое списка m_ids предыдущего ReadView равно [100, 200] и min_trx_id равен 100, max_trx_id равен 201, Creator_trx_id равен 0.
  • Затем выберите видимые записи из цепочки версий.Значение trx_id этой версии равно 100, которое находится в списке m_ids, поэтому оно не соответствует требованиям видимости.
  • В следующей версии значение trx_id этой версии равно 200, что тоже есть в списке m_ids, поэтому не соответствует требованиям, и продолжает переходить к следующей версии.
  • Значение trx_id следующей версии равно 90, что меньше значения min_trx_id, равного 100, в ReadView, поэтому эта версия соответствует требованиям.

Оригинальная ссылкаСтатья поможет вам освоить согласованное представление mysql (MVCC)