предисловие
Если транзакции в базе данных выполняются последовательно, этот метод может гарантировать, что не будет исключений и ошибок при выполнении транзакций, но проблема в том, что последовательное выполнение приведет к узким местам в производительности; Контроль может вызвать множество проблем, включая взаимоблокировки, потерю обновления и многое другое. Это требует от нас найти разумный компромисс между производительностью и безопасностью и использовать соответствующие механизмы управления параллелизмом для обеспечения выполнения параллельных транзакций.
Проблемы с одновременными транзакциями
Во-первых, давайте взглянем на проблемы, которые могут быть связаны с одновременными транзакциями. Параллельные транзакции, обращающиеся к одной и той же записи, можно условно разделить на следующие три ситуации:
- читать-читать: то есть параллельные транзакции последовательно считывают одну и ту же запись;
- пиши-пиши: то есть параллельные транзакции последовательно изменяют одну и ту же запись;
- пиши-читайиличитай пиши: две параллельные транзакции выполняют операции чтения и записи для одной и той же записи соответственно.
читать-читать
Поскольку запись чтения не окажет никакого влияния на запись, поэтому одна и та же транзакция одновременно читает одну и ту же запись, не существует никаких проблем с безопасностью, поэтому разрешите эту операцию.
пиши-пиши
Если параллельным транзакциям разрешено читать одну и ту же запись и последовательно изменять запись на основе старого значения, то модификация, сделанная предыдущей транзакцией, будет перезаписана модификацией более поздней транзакции, то есть возникновениеЗафиксировать покрытиеПроблема.
В другом случае параллельные транзакции последовательно вносят изменения в одну и ту же запись, и после того, как одна транзакция зафиксирована, другая транзакция откатывается, поэтому будет проблема, что зафиксированные изменения теряются из-за отката, т.е.наложение откатапроблема.
Обе эти проблемы приводят к потере обновлений, когда перезаписывается откат.Потерянные обновления категории 1проблема, покрытие фиксации называетсяВторая категория потерянных обновленийпроблема.
пиши-читайиличитай пиши
Эта ситуация более сложная и наиболее подвержена проблемам.
Возникает, если одна транзакция читает незафиксированные записи модификации другой транзакции.грязное чтениеПроблема;
Если мы контролируем это так, чтобы транзакция могла только читать измененные данные других совершенных транзакций, то данные, считанные этой транзакцией до и после того, как другая транзакция фиксирует модификацию, не совпадают, что означает, что возникновениенеповторяемое чтение;
Если транзакционный запрос основан на некоторых условиях к некоторым записям, а затем вставляет другие вещи, которые некоторые записи в таблицу, исходные результаты транзакции не согласуются с результатами, полученными в первом запросе, чтобы снова получить запрос в тех же условиях, которые значит случилосьгаллюцинации.
уровень изоляции транзакций
Для проблем, которые могут возникнуть при выполнении параллельных транзакций, упомянутых выше, их серьезность также различна, мы можем отсортировать их по серьезности проблем:
丢失更新 > 脏读 > 不可重复读 > 幻读
Таким образом, если мы сможем допустить некоторые менее серьезные проблемы, мы сможем получить некоторый прирост производительности. Таким образом, существует четыре уровня изоляции транзакций:
- Читайте незарегистрированные (
Read Uncommitted
): разрешить незафиксированную запись чтения, происходит грязное чтение, неповторяемое чтение, фантомное чтение; - читать зафиксировано (
Read Committed
): Разрешены только отправленные записи, грязные чтения не будут происходить, но будут происходить повторные чтения и фантомные чтения; - повторяющееся чтение (
Repeatable Read
): Грязные чтения и неповторяющиеся чтения не будут происходить, но будут происходить фантомные чтения; ноMySQL
Эксплойт на этом уровне изоляцииMVCCилигэп замокМожет предотвратить возникновение проблем с фантомным чтением; - Сериализуемый (
Serializable
): То есть транзакция выполняется последовательно, и вышеперечисленных проблем естественно не возникнет.
Стоит отметить, что вышеописанные четыре уровня изоляции не имеют проблемы покрытия откатами, но проблема покрытия фиксации не является проблемой.MySQL
Например, вRead Uncommitted
,Read Committed
а такжеRepeatable Read
Встречается на всех трех уровнях изоляции (стандартныйRepeatable Read
Уровень изоляции не позволяет решить проблему покрытия фиксации), и для решения этой проблемы требуются дополнительные блокировки.
Реализация уровней изоляции
SQL
Спецификация определяет вышеупомянутые четыре уровня изоляции, но не дает, как реализовать четыре уровня изоляции, поэтому реализация и использование разных баз данных также различны. иSQL
Стандарт уровня изоляции основан на реализации на основе блокировки, потому что необходимо сначала понять, как реализуется традиционный уровень изоляции на основе блокировки.
Реализация традиционных уровней изоляции
Поскольку традиционный уровень изоляции основан на блокировках, давайте сначала разберемся с блокировками.
Замок
Традиционные замки бывают двух типов:
- общий замок (
Shared Locks
): короткое имяS锁
, когда транзакция читает запись, она должна сначала получить общую блокировку записи. - эксклюзивный замок (
Exclusive Locks
): короткое имяX锁
Когда транзакция записывает запись, она должна сначала получить эксклюзивный замок на записи.
Следует отметить, что другие транзакции также могут получить совместную блокировку записи с общей блокировкой, но не могут получить монопольную блокировку записи, т.е.S锁
иS锁
совместим,S锁
иX锁
несовместимы; и для записи с эксклюзивной блокировкой другие транзакции не могут получить ни общую блокировку записи, ни эксклюзивную блокировку, т.X锁
иX锁
тоже несовместимо.
Кроме того, только что упоминалось, что когда транзакция читает запись, ей необходимо получитьS锁
, но иногда транзакции необходимо предотвратить доступ других транзакций к записи при чтении записи, тогда ей необходимо получитьX锁
. отMySQL
Например, есть два способа блокировки чтения:
- Добавлять в запись при чтении
S锁
:
SELECT ... LOCK IN SHARE MODE;
Если транзакция выполняет инструкцию, она добавит к прочитанной записиS锁
Это позволяет другим транзакциям получить записьS锁
; и если другим транзакциям нужно получить записьX锁
, то вам нужно дождаться фиксации текущей транзакции и освободить ееS锁
.
- Добавлять в запись при чтении
X锁
:
SELECT ... FOR UPDATE;
Если транзакция выполняет инструкцию, она добавит к прочитанной записиX锁
Эта другая транзакция хочет сказать записиS锁
илиX锁
, то вам нужно дождаться фиксации текущей транзакции и освободить ееX锁
.
По степени детализации замки можно разделить на два типа:
- Блокировка строки: блокируется только одна строка записей, записи в других строках не затрагиваются.
- Блокировка таблицы: заблокируйте всю таблицу, это повлияет на все операции в таблице.
Уровень изоляции на основе замков
В реализации на основе блокировки разница между четырьмя уровнями изоляции заключается в разнице в методах блокировки:
-
читать незафиксированные: Операция чтения не заблокирована, чтение-чтение, чтение-запись и запись-чтение параллельны, добавлена операция записи
X锁
И он не освобождается, пока транзакция не будет зафиксирована. -
чтение зафиксировано: операция чтения плюс
S锁
, операции записи плюсX锁
И он не будет освобожден до тех пор, пока транзакция не будет зафиксирована; операции чтения не будут блокировать другие транзакции от чтения или записи, а операции записи будут блокировать другие транзакции от записи и чтения, тем самым предотвращая проблему грязного чтения. -
повторяемое чтение: операция чтения плюс
S锁
И не освобождается, пока транзакция не будет зафиксирована, а операция записи добавляетX锁
И он не будет выпущен до тех пор, пока транзакция не будет зафиксирована; операция чтения не будет блокировать чтение других транзакций, но заблокирует запись других транзакций, а операция записи заблокирует чтение и запись других транзакций, поэтому она может предотвратить грязные чтения и неповторяющиеся чтения. -
сериализовать: добавлены операции чтения и записи.
X锁
И он не освобождается до тех пор, пока транзакция не будет зафиксирована, а гранулярность — это блокировка таблицы, которая является строго последовательной.
Вот некоторые детали, на которые стоит обратить внимание:
- Если блокировка получена и не снимается до тех пор, пока транзакция не будет зафиксирована, блокировка вызываетсядлинный замок; если блокировка снимается после завершения операции, вызывается блокировкакороткий замок. Например, на уровне изоляции read commited операции чтения добавляют
S锁
Для короткой блокировки добавлена операция записиX锁
для длинных замков. - Для повторяемых уровней изоляции чтения и сериализуемых операций чтения добавляются
S锁
и добавлены операции записиX锁
Оба являются длинными блокировками, то есть после того, как транзакция получает блокировку, она не может быть освобождена до тех пор, пока транзакция не будет зафиксирована.Этот протокол, который делит получение блокировок и освобождение блокировок на два разных этапа, называетсядвухфазный замокпротокол(2-phase locking
). Протокол двухфазной блокировки предусматривает, что на этапе блокировки транзакция может получить блокировку, но не может снять блокировку, а на этапе разблокировки транзакция может только снять блокировку и не может получить новые блокировки. Протокол двухфазной блокировки может обеспечить сериализацию транзакций и решить проблему параллелизма транзакций, но он также значительно увеличивает вероятность взаимоблокировок.
Реализация уровня изоляции MySQL
различные базы данных дляSQL
Поддержка уровня изоляции, указанная в стандарте, отличается.Хотя ядро базы данных реализует уровень изоляции, максимально приближенный к стандартной спецификации уровня изоляции, он все же несколько отличается от стандартного ожидания.
MySQL
(InnoDB
) поддерживает четыре уровня изоляции, которые несколько отличаются от проблем, допускаемых стандартными уровнями изоляции на всех уровнях, таких какMySQL
При уровне изоляции повторяющегося чтения проблема фантомного чтения может быть предотвращена, но также может возникнуть проблема покрытия фиксации.
По сравнению с традиционной реализацией уровней изоляции на основе блокировки,MySQL
черезMVCC
(многоверсионный контроль параллелизма) для обеспечения контроля параллелизма чтения-записи и двухфазные блокировки для обеспечения контроля параллелизма записи-записи.MVCC
это безблокировочная схема для разрешения транзакцийчитай пишиПроблемы параллелизма могут быть значительно улучшенычитай пишиВыполнение параллельных операций.
Принцип реализации MVCC
Для удобства описания сначала составим таблицуbook
, всего три поля, которые являются первичным ключомbook_id
, названиеbook_name
, в наличииstock
. Затем вставьте некоторые данные в таблицу:
INSERT INTO book VALUES(1, '数据结构', 100);
INSERT INTO book VALUES(2, 'C++指南', 100);
INSERT INTO book VALUES(3, '精通Java', 100);
цепочка версий
Для использованияInnoDB
Таблица механизма хранения содержит два важных скрытых столбца в своей записи кластеризованного индекса:
- Номер транзакции (
DB_TRX_ID
): Всякий раз, когда транзакция изменяет запись в кластеризованном индексе, идентификатор текущей транзакции записывается вDB_TRX_ID
середина. - указатель отката (
DB_ROLL_PTR
): Всякий раз, когда транзакция изменяет запись в кластеризованном индексе, она записывает старую версию записи вundo
войти, черезDB_ROLL_PTR
Этот указатель можно использовать для получения информации о старой версии записи.
Если запись изменяется несколько раз в рамках транзакции, каждая модификация генерируетundo
журнал, и этиundo
проход журналаDB_ROLL_PTR
Указатели объединяются в цепочку версий, головной узел цепочки версий — это последнее значение записи, а хвостовой узел — начальное значение в начале транзакции.
Например, мы в таблицеbook
Сделайте следующие модификации:
BEGIN;
UPDATE book SET stock = 200 WHERE id = 1;
UPDATE book SET stock = 300 WHERE id = 1;
Такid=1
Цепочка версий записи в это время показана на следующем рисунке:
ReadView
Для использованияRead Uncommitted
Для транзакций на уровне изоляции вам нужно только прочитать последнюю версию записи в цепочке версий; для транзакций, использующихSerializable
Для транзакций уровня изоляцииInnoDB
Используйте блокировки для доступа к записям. иRead Committed
иRepeatable Read
С точки зрения уровня изоляции необходимо прочитать записи, измененные зафиксированной транзакцией, то есть, если модификация версии в цепочке версий не отправлена, записи этой версии не могут быть прочитаны. Следовательно, необходимо определитьRead Committed
иRepeatable Read
На уровне изоляции какая версия в цепочке версий может быть прочитана текущей транзакцией. тогдаReadView
Для решения этой проблемы предлагается концепция.
ReadView
Эквивалент снимка записи расписания, в этом снимке мы можем получить транзакции, связанные с текущей записью, какие транзакции были зафиксированы.стабильные дела, которыеАктивная транзакция, которыеТранзакции, которые запускаются после создания моментального снимка. Из этого мы можемАлгоритм сравнения видимостиОпределите последнюю запись версии, которую можно прочитать в цепочке версий.
Алгоритм сравнения видимостиАлгоритм сравнения основан на идентификаторе транзакции. Во-первых, нам нужно знать следующее: Транзакцияid
распределяется постепенно. отReadView
Мы можем получить наименьшую и наибольшую транзакцию среди активных транзакций в системе на момент создания снимкаid
(крупнейшая сделкаid
на самом деле тот, который будет назначен следующей транзакции в системеid
значение), поэтому мы получаем активную транзакциюid
Диапазон, мы можем назвать этоACTIVE_TRX_ID_RANGE
. Тогда транзакции, соответствующие идентификаторам транзакций меньше этого диапазона, являются зафиксированными стабильными транзакциями, а транзакции, превышающие этот диапазон, являются транзакциями, которые открываются после создания моментального снимка.ACTIVE_TRX_ID_RANGE
В дополнение к активной транзакции транзакции в области также являются зафиксированными стабильными транзакциями.
С приведенной выше информацией мы следуем по цепочке версий от головного узла, чтобы найти последнюю запись версии, которую можно прочитать:
1. Сначала определите запись версииDB_TRX_ID
Поля и генерацияReadView
Совпадают ли идентификаторы транзакций, соответствующие транзакциям. Если они равны, то это означает, что данная версия записи генерируется в текущей транзакции и естественно может быть прочитана текущей транзакцией, иначе переходим к шагу 2.
2. Если версия записанаDB_TRX_ID
Поле меньше диапазонаACTIVE_TRX_ID_RANGE
, указывающее, что запись версии — это запись, измененная зафиксированной транзакцией, то есть видимая для текущей транзакции, иначе переходим к следующему шагу.
3. Если версия записанаDB_TRX_ID
Поле находится в пределах досягаемостиACTIVE_TRX_ID_RANGE
Если идентификатор транзакции не соответствует активной транзакции, это указывает на то, что запись версии является записью, измененной зафиксированной транзакцией, то есть она видна текущей транзакции; если идентификатор транзакции соответствует активной транзакции, он не видна для текущей транзакции Чтение Для записи следующей версии в цепочке версий повторяйте описанные выше шаги, пока не будет найдена версия, видимая для текущей транзакции.
Если в результате вышеописанных шагов определено, что запись версии является видимой для текущей транзакции, результат запроса возвращает эту запись версии; в противном случае считывается следующая запись версии, которая продолжает оцениваться в соответствии с вышеуказанными шагами до тех пор, пока не будет достигнут конечный узел транзакции. цепочка версий. Если после обхода цепочки версий не найдена версия, видимая для текущей транзакции, результат запроса будет пустым.
существуетMySQL
середина,Read Committed
иRepeatable Read
Разница на уровне изоляции заключается в том, что они генерируютReadView
сроки разные.
MVCC обеспечивает различные уровни изоляции
сказал раньшеReadView
Механизм только вRead Committed
иRepeatable Read
Это вступает в силу на уровне изоляции, поэтому только эти два уровня изоляции имеютMVCC
.
существуетRead Committed
На уровне изоляции он будет генерироваться каждый раз при чтении данных.ReadView
; пока вRepeatable Read
Уровень изоляции будет создан только тогда, когда транзакция считывает данные в первый раз.ReadView
, последующие операции чтения будут использовать этоReadView
.
Давайте посмотрим на примерRead Committed
иRepeatable Read
ниже уровня изоляцииMVCC
разная производительность. Мы продолжаем показыватьbook
Покажите как пример.
Читать Анализ уровня изоляции
предположим вRead Committed
На уровне изоляции выполняются следующие транзакции: транзакцияid
на 10:
BEGIN; // 开启Transaction 10
UPDATE book SET stock = 200 WHERE id = 2;
UPDATE book SET stock = 300 WHERE id = 2;
На данный момент транзакция не была зафиксирована,id
Цепочка версий записи для 2 показана ниже:
Затем мы запускаем пару транзакцийid
Запрос для 2 записей:
BEGIN;
SELECT * FROM book WHERE id = 2;
при исполненииSELECT
оператор будет генерироватьReadView
,ДолженReadView
серединаACTIVE_TRX_ID_RANGE
за[10, 11)
, идентификатор текущей транзакцииcreator_trx_id
за0
(Поскольку при выполнении операции записи внутри транзакции выделяется отдельная транзакция.id
, иначе транзакцияid
за0
). как мы уже говорилиReadView
Принцип работы запрошенной записи версии:
+----------+-----------+-------+
| book_id | book_name | stock |
+----------+-----------+-------+
| 2 | C++指南 | 100 |
+----------+-----------+-------+
Тогда мы будем делоid
Совершить 10 транзакций:
BEGIN; // 开启Transaction 10
UPDATE book SET stock = 200 WHERE id = 2;
UPDATE book SET stock = 300 WHERE id = 2;
COMMIT;
Начать выполнение другой транзакции в то же времяid
за11
транзакции, но не совершать:
BEGIN; // 开启Transaction 11
UPDATE book SET stock = 400 WHERE id = 2;
В настоящее времяid
Цепочка версий записи для 2 показана ниже:
Затем мы возвращаемся к транзакции запроса только сейчас, чтобы снова запроситьid
за 2 записи:
BEGIN;
SELECT * FROM book WHERE id = 2; // 此时Transaction 10 未提交
SELECT * FROM book WHERE id = 2; // 此时Transaction 10 已提交
при выполнении во второй разSELECT
оператор снова сгенерируетReadView
,ДолженReadView
серединаACTIVE_TRX_ID_RANGE
за[11, 12)
, идентификатор текущей транзакцииcreator_trx_id
все еще0
. в соответствии сReadView
Чтобы проанализировать принцип работы, запрошенная нами запись версии выглядит следующим образом:
+----------+-----------+-------+
| book_id | book_name | stock |
+----------+-----------+-------+
| 2 | C++指南 | 300 |
+----------+-----------+-------+
Его можно найти из приведенного выше анализа, потому что каждый раз, когда выполняется оператор запроса, создается новыйReadView
, так что вRead Committed
Транзакция на уровне изоляции считывает данные, измененные зафиксированной транзакцией, в расписании запроса.
Анализ уровня изоляции Repeatable Read
мы вRepeatable Read
Повторите описанную выше транзакционную операцию на уровне изоляции:
BEGIN; // 开启Transaction 20
UPDATE book SET stock = 200 WHERE id = 2;
UPDATE book SET stock = 300 WHERE id = 2;
На данный момент транзакция еще не зафиксирована, и тогда мы открываем пару транзакцийid
Запрос для 2 записей:
BEGIN;
SELECT * FROM book WHERE id = 2;
Когда транзакция выполняется впервыеSELECT
оператор будет генерироватьReadView
,ДолженReadView
серединаACTIVE_TRX_ID_RANGE
за[10, 11)
, идентификатор текущей транзакцииcreator_trx_id
за0
. в соответствии сReadView
Принцип работы запрошенной записи версии:
+----------+-----------+-------+
| book_id | book_name | stock |
+----------+-----------+-------+
| 2 | C++指南 | 100 |
+----------+-----------+-------+
Тогда мы будем делоid
Совершить 20 транзакций:
BEGIN; // 开启Transaction 20
UPDATE book SET stock = 200 WHERE id = 2;
UPDATE book SET stock = 300 WHERE id = 2;
COMMIT;
Начать выполнение другой транзакции в то же времяid
Транзакция за 21, но не совершена:
BEGIN; // 开启Transaction 21
UPDATE book SET stock = 400 WHERE id = 2;
Затем мы возвращаемся к транзакции запроса только сейчас, чтобы снова запроситьid
за 2 записи:
BEGIN;
SELECT * FROM book WHERE id = 2; // 此时Transaction 10 未提交
SELECT * FROM book WHERE id = 2; // 此时Transaction 10 已提交
при выполнении во второй разSELECT
оператор не создает новыйReadView
, будет по-прежнему генерироваться при использовании первого запросаReadView
. Следовательно, запрашиваемая нами запись версии совпадает с результатом первого запроса:
+----------+-----------+-------+
| book_id | book_name | stock |
+----------+-----------+-------+
| 2 | C++指南 | 100 |
+----------+-----------+-------+
Из вышеприведенного анализа видно, что, посколькуRepeatable Read
Транзакции на уровне изоляции будут генерироваться только при первом выполнении запроса.ReadView
, последующие операции запроса в транзакции будут использовать этоReadView
, поэтому один и тот же запрос выполняется несколько раз в следующей транзакции на этом уровне изоляции, и результат остается тем же, что обеспечивает повторяемость чтения.
Чтение моментального снимка и текущее чтение
Снимок прочитан
существуетRead Committed
иRepeatable Read
Под уровнем изоляции обычныйSELECT
запрос прочитанMVCC
Версия в цепочке версий эквивалентна чтению моментального снимка, поэтому она называетсяСнимок прочитан. Этот способ чтения не блокируется, поэтому операция чтения является неблокирующей, поэтому ее также называютнеблокирующее чтение.
в стандартеRepeatable Read
Операции чтения на уровне изоляции увеличатсяS锁
, до конца транзакции, тем самым блокируя запись другими транзакциями; но вMySQL
изRepeatable Read
Операция чтения под уровнем изоляции не заблокирована и не помешает другим транзакциям записать ту же запись, поэтому в последующей операции записи можно записать результат, рассчитанный на основе старых данных в цепочке версий, что приводит к проблеме покрытия фиксации. Чтобы избежать этой проблемы, вам нужно добавить дополнительные блокировки для достижения.
текущее чтение
упоминалось ранееMySQL
Есть два способа блокировки чтения:
SELECT ... LOCK IN SHARE MODE; // 读取时对记录加S锁,直到事务结束
SELECT ... FOR UPDATE; // 读取时对记录加X锁,直到事务结束
Этот метод чтения считывает текущую последнюю версию записи, называемуютекущее чтение. Также дляINSERT
,DELETE
,UPDATE
операции, также необходимо сначала прочитать запись и получить записьX锁
, этот процесс также является текущим чтением. Поскольку запись должна быть заблокирована, она будет блокировать операцию записи других транзакций, поэтому ее также называютблокировка чтенияилиблокировка чтения.
Текущее чтение не только добавляет к текущей записиблокировка записи строки, также добавит данные в пространство диапазона запросагэп замок(GAP LOCK
), тем самым предотвращая проблемы с фантомным чтением.
Суммировать
В данной статье представлены различные проблемы параллелизма транзакций, а также уровни изоляции, используемые для избежания проблем разной степени, и более подробно описана реализация традиционных уровней изоляции иMySQL
Как реализованы уровни изоляции. Однако механизм параллелизма базы данных относительно сложен. В этой статье представлено только общее описание и введение. Многие детали по-прежнему требуют от читателей запроса соответствующей информации для более подробного понимания.