Гарантия согласованности параллельных данных чтения и записи (2) — MySQL

MySQL

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

Как элегантно управлять параллельными данными? По сути нужно решить две проблемы:

  • конфликт чтения-записи
  • конфликт записи-записи

Давайте посмотрим, как наиболее распространенный движок хранения MySQL InnoDB решает две вышеупомянутые проблемы?

Параллельные транзакции

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

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

При решении проблемы конфликта параллелизма сосредоточьтесь на изоляции и согласованности при параллельных транзакциях.

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

Уровень изоляции транзакции

Сериализуемая сериализация

Спецификация SQL

Как следует из названия, сериализует порядок выполнения параллельных транзакций.

Транзакция А Транзакция Б Транзакция С
begin begin begin
select/insert/update/delete
commit
select/insert/update/delete
commit
select/insert/update/delete
commit
Реализация InnoDB

Эксклюзивные блокировки на уровне таблицы добавляются как для чтения, так и для записи.

фактический эффект

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

Повторяемое чтение Повторяемое чтение (по умолчанию)

Спецификация SQL

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

Транзакция А Транзакция Б
begin begin
select a from table where id =1 => a =1 update table set a = 2 where id = 1
commit
select a from table where id =1 => a =1
commit
Реализация InnoDB

На этом уровне MVCC (Multi-Version Concurrency Control) обрабатывает конфликты чтения-записи, 2PL (двухфазная блокировка) обрабатывает конфликты записи-записи.

  • MVCC
SELECT * FROM table WHERE id = 1

Приведенный выше обычный оператор Select выполняет чтение моментального снимка на уровне Repeatable Read. Механизм MVCC добавляет два дополнительных поля к каждому фрагменту данных, одно используется для записи идентификатора текущей транзакции (новая транзакция увеличивается), а другое используется для указания на предыдущую версию в цепочке версий журнала отмен (каждый раз транзакция обновляет запись, mysql запишет журнал отмены для облегчения отката транзакции) и откроет ReadView для транзакции, чтобы отобразить текущий активный идентификатор транзакции.

Предположим, что исходная запись а создается отправкой транзакции Insert, а транзакция 101 обновляет значение а дважды подряд, но не отправляет его.В это время транзакция 102 должна запросить значение а, создать ReadView в начало транзакции, запишите текущую активную транзакцию [101] и выполните поиск. Запись =4 обнаруживает, что идентификатор транзакции равен 101, который находится в ReadView, поэтому следуйте по цепочке версий, чтобы найти идентификатор первой транзакции a =1, которого нет в ReadView, и вернуть

В это время, даже если транзакция 101 зафиксирована, поскольку ReadView транзакции 102 все еще [101] в момент транзакции, последующее чтение все еще равно а=1.

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

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

SELECT * FROM table WHERE id = 1 lock in share mode	//加读锁
SELECT * FROM table WHERE id = 1 for update	//加写锁
  • 2PL

Этот тип данных операции должен считывать текущую версию данных, которая называется текущим чтением, включая явную блокировку SELECT и неявную блокировку записи INSERT, UPDATE, DELETE.

На этапе блокировки 2PL разрешены только операции блокировки. Существующая блокировка чтения может быть добавлена ​​к блокировке чтения, но не к блокировке записи. Если есть блокировка записи, дополнительные блокировки не могут быть добавлены. Транзакции, которые не могут получить блокировки, могут только блокировать и ждать, пока Транзакция, удерживающая блокировку, не сможет продолжать конкурировать за блокировку и выполняться после снятия блокировки на этапе разблокировки (фиксация транзакции).

По сравнению с Serializable блокировками на уровне таблицы, Repeatable Read имеет более точную детализацию блокировки. Это блокировка на уровне строки. Она блокирует только необходимые строки данных и поддерживает более высокий параллелизм, но если запрос требует слишком много данных или не выполняется, индексы по-прежнему будет обновляться до блокировок на уровне таблицы (блокировки строк фактически добавляются в индекс, если индекса нет, вся таблица будет заблокирована)

фактический эффект

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

В дополнение к SELECT, UPDATE и DELETE операции с базой данных также включают INSERT.В спецификации SQL этот уровень INSERT вызовет возможность фантомного чтения: поскольку это блокировка на уровне строки, после транзакции A добавляется блокировка строки в обновить определенный диапазон данных, транзакция B вставляет запись в этот диапазон.В это время транзакция A находит еще одни данные, и они не обновляются сами по себе

На самом деле, в реализации MySQL GAP-блокировки (gap-блокировки) используются для блокировки в определенном интервале индекса, блокировки даже ненужных данных и предотвращения вставки в него данных другими транзакциями, так что Next-Key, состоящий из блокировок строк + Блокировка GAP Блокировка также предотвращает изменение, удаление и вставку других транзакций, что решает проблему фантомного чтения

Глубокое понимание

MVCC лежит в основе реализации Repeatable Read, но наткнулся наMySQL · Анализ исходного кода · Большая разница в уровне изоляции InnoDB Repeatable ReadНаходить:

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

На уровне RR есть феномен, который невозможно перечитать.

Транзакция А Транзакция Б
begin begin
select a from table where id = 1 => null
insert into table(id,a) values(1,1)
commit
select a from table where id = 1 => null
update table set a = 2 where id = 1
select a from table where id = 1 => a = 2
commit

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

Чтение зафиксировано

Спецификация SQL

Транзакции могут считывать данные, уже зафиксированные другими транзакциями.

Транзакция А Транзакция Б
begin begin
select a from table where id = 1 => a=1
update table set a = 2 where id = 1
select a from table where id = 1 => a=1 commit
select a from table where id = 1 => a=2
commit
Реализация InnoDB

Подобно уровню RR, уровень RC также использует MVCC для обработки конфликтов чтения-записи и 2PL для обработки конфликтов записи-записи.

  • MVCC

На этом уровне обычный оператор Select по-прежнему является чтением моментального снимка. Самое большое отличие от RR заключается в том, что RC генерирует ReadView каждый раз, когда он выбирает. Например, ReadView, созданный при первом выборе транзакции 103, имеет вид [101, 102]. транзакция 101 зафиксирована, транзакция 103 снова выбирает новый ReadView, сгенерированный как [102], вы можете прочитать данные, отправленные 101

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

Точно так же чтение снимка также может быть историческими данными.Если изменения, сделанные на основе исторических данных, будут обновлены, обновление будет потеряно.Решение такое же, как оптимистическая блокировка и пессимистическая блокировка на уровне RR.

  • 2PL

Операции Update, Insert, Delete также добавят блокировки записи, разница в том, что на этом уровне есть только блокировки строк, а Gap-блокировки и Next-Key блокировки отсутствуют, поэтому неправильные операции вызовут фантомное чтение.

фактический эффект

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

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

Просто требуется немного больше усилий, чтобы контролировать согласованность

Читать незафиксированные

Спецификация SQL

Транзакции могут считывать незафиксированные данные из других транзакций.

Транзакция А Транзакция Б
begin begin
select a from table where id = 1 => a=1
update table set a = 2 where id = 1
select a from table where id = 1 => a=2
commit commit
Реализация InnoDB

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

фактический эффект

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

резюме

База данных представляет собой приложение с высокой степенью параллелизма с различными сценариями. Один набор стандартов не может преобладать. Поэтому разработчикам предоставляется четыре уровня изоляции для взвешивания, но в основном есть два типа RR и RC.

Чтобы обеспечить согласованность записи, необходимо добавить блокировки, будь то блокировка, которая поставляется с Insert, Update, Delete или select... Для режима обновления/в совместном использовании блокировка на уровне управления бизнесом, чтобы уменьшить воздействие замка, необходимо Контролировать охват замка

Что касается согласованности чтения, если вы посмотрите на принцип 28, вы можете просто подумать, что 80% операций — это операции только для чтения.В этом сценарии, даже если вы видите исторические данные в первый раз, обновите их несколько раз.Вы можете всегда видеть самые свежие данные. Если вам нужна блокировка для обеспечения строгой согласованности, это еще больше повлияет на производительность. Поэтому основные реляционные базы данных, включая MySQL, используют типичное решение без блокировки MVCC для достижения слабой согласованности чтения.

Рекомендуемое чтение

Связь между уровнем изоляции транзакций и блокировкой в ​​Innodb

Уровень изоляции транзакций и использование транзакций Mysql

Реализация транзакций в MySQL

Анализ блокировки MySQL

Уровень изоляции транзакций MySQL и MVCC

Боевые заметки MySQL — глубокое понимание уровня изоляции RR