Может ли повторяемый уровень чтения MySQL решить проблему фантомного чтения?

MySQL

введение

Когда раньше у меня было глубокое понимание теории баз данных, я узнал о возможных проблемах разных уровней изоляции транзакций. Для лучшего понимания протестируйте и воспроизведите эти проблемы в базе данных MySQL. Грязные чтения и неповторяемые чтения легко воспроизводятся на соответствующем уровне изоляции. Но для фантомного чтения я обнаружил, что оно не отображается на уровне изоляции повторяемого чтения.В то время я думал о том, что MySQL сделал с фантомным чтением?

контрольная работа:

Создайте отдел таблицы для тестирования:

CREATE TABLE `dept` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8

insert into dept(name) values("后勤部")
Транзакция 1 Транзакция 2
begin begin
select * from dept
- вставить в значение отдела(название)("Отдел НИОКР")
- commit
select * from dept
commit

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

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

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

Теория принципа базы данных

дела

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

Зачем иметь дело

Транзакция базы данных обычно состоит из последовательности операций чтения или записи в базу данных. Он существует для двух целей:

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

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

Особенности сделки

Транзакции обладают четырьмя свойствами: атомарность, непротиворечивость, изоляция и долговечность. Эти четыре свойства часто называют свойствами ACID.

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

  • Последовательность: Транзакция должна перевести базу данных из одного согласованного состояния в другое. Непротиворечивость и атомарность тесно связаны.

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

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

Несколько свойств между транзакциями не являются равным набором понятий:

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

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

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

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

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

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

  • Фантомное чтение. Фантомное чтение — это явление, которое возникает, когда транзакция не выполняется независимо. Например, транзакция T1 изменяет элемент данных всех строк в таблице с «1» на «2». В это время транзакция T2 вставляет строку элемента данных в эту таблицу, и значение этого элемента данных по-прежнему равно «1». " и фиксируется в базе данных. Если пользователь, работающий с транзакцией T1, проверит только что измененные данные, он обнаружит, что есть одна строка, которая не была изменена.На самом деле эта строка добавлена ​​из транзакции T2, как будто произошли галлюцинации.Это чтение галлюцинации. И фантомное чтение, и неповторяемое чтение считывают другую зафиксированную транзакцию (это отличается от грязного чтения), разница в том, что неповторяющееся чтение может происходить в операциях обновления и удаления, а фантомное чтение происходит в операциях вставки.

эксклюзивная блокировка, общая блокировка

Эксклюзивная блокировка (Exclusive), также известная как блокировка X, блокировка записи.

Общая блокировка (Shared), также известная как блокировка S, блокировка чтения.

Отношения между блокировками чтения-записи следующие:

  • Транзакция добавляет блокировку S к объекту данных O и может выполнять операции чтения для O, но не может выполнять операции обновления. В течение периода блокировки другие транзакции могут добавлять S-блокировки к O, но не могут добавлять X-блокировки.
  • Транзакция добавляет блокировку X к объекту данных O и может читать и обновлять O. В течение периода блокировки другие транзакции не могут добавлять блокировки к O.

То есть взаимосвязь между блокировками чтения-записи можно резюмировать следующим образом: многократное чтение и одиночная запись

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

Внутри транзакции существуют следующие уровни изоляции:

  • Чтение незафиксированных (Read Uncommitted): решение проблемы потерянных обновлений. Если транзакция начала операцию записи, другим транзакциям не разрешается писать в то же время, но другим транзакциям разрешено читать эту строку данных. Этот уровень изоляции может быть достигнут за счет «исключительных блокировок записи», то есть, если транзакции необходимо изменить некоторые данные, к этим данным должны быть добавлены X-блокировки, а для чтения данных S-блокировки не требуются.

  • Read Committed: решена проблема грязного чтения. Транзакции, которые читают данные, позволяют другим транзакциям продолжать доступ к строке, но незафиксированные транзакции записи не позволяют другим транзакциям получить доступ к строке. Этого можно добиться за счет «мгновенных общих блокировок чтения» и «эксклюзивных блокировок записи», то есть, если транзакции необходимо модифицировать какие-то данные, к этим данным необходимо добавить X-блокировки, а при чтении данных — S-блокировки. Снимите блокировку S, не дожидаясь завершения транзакции.

  • Повторяемое чтение: неповторяющиеся и грязные чтения запрещены, но иногда могут возникать фантомные чтения. Транзакции, которые считывают данные, запрещают транзакции записи (но разрешают транзакции чтения), а транзакции записи запрещают любые другие транзакции. Mysql использует этот уровень изоляции по умолчанию. Этого можно добиться с помощью «разделяемых блокировок чтения» и «эксклюзивных блокировок записи», то есть, если транзакции необходимо модифицировать какие-то данные, к этим данным необходимо добавить блокировки X, а при чтении данных — S-блокировки. Чтение данных завершено, то не сразу снимите блокировку S, а дождитесь окончания транзакции, чтобы освободить ее.

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

Реализация уровней изоляции в MySQL

Приведенный выше контент объясняет некоторые концепции теории баз данных, но в таких базах данных, как MySQL и ORACLE, из соображений производительности он не полностью реализован в соответствии с представленной выше теорией.

MVCC

Multi-Version Concurrency Control (MVCC) — это способ реализации уровней изоляции, основанный на оптимистической теории блокировки в MySQL, который используется для реализации уровней изоляции зафиксированного чтения и повторяемого чтения.

Реализация (уровень изоляции - повторяемое чтение)

Прежде чем говорить о том, как его реализовать, введем два понятия:

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

Номер версии транзакции: номер версии системы на момент запуска транзакции.

В MySQL после каждой части данных в таблице добавляются два поля:

Создать номер версии: при создании строки данных назначьте текущий номер версии системы в качестве номера версии создания.

Удалить номер версии: при удалении строки данных назначьте текущий номер версии системы в качестве номера версии для удаления.

SELECT

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

Номер версии создания

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

INSERT

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

UPDATE

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

DELETE

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

Действительно ли MVCC решает проблему фантомного чтения?

С самого начала нашего тестового примера и приведенной выше теоретической поддержки кажется, что проблема фантомного чтения решается в MySQL через MVCC, так как это сериализованное чтение кажется бессмысленным, продолжаем тестировать с сомнениями.

Данные перед тестом:

测试前数据

Транзакция 1 Транзакция 2
begin begin
select * from dept
- вставить в значение отдела(название)("Отдел НИОКР")
- commit
update dept set name="Финансовый отдел" (если вы не хотите, чтобы вас уволили с работы, вы должны написать, где условия)
commit

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

id  name
1   财务部
2   研发部

Но что мы на самом деле прошли, так это:

测试后数据

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

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

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

выбрать снимок прочитать

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

текущее чтение

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

Текущее чтение select требует ручной блокировки:

select * from table where ? lock in share mode;
select * from table where ? for update;

Существует проблема

Сначала во время тестирования я думал использоватьbeginОператор должен запустить транзакцию, поэтому во втором тесте выше, поскольку транзакция 1 стартовала первой, новые данные транзакции 2 были найдены в транзакции 1. В то время считалось, что это несовместимо с правилом выбора в предыдущего MVCC., поэтому были проведены следующие тесты:

image

SELECT * FROM information_schema.INNODB_TRX //用于查询当前正在执行中的事务

Вы можете видеть, что если вы просто выполнитеbeginОператор фактически не запускает транзакцию.

ниже вbeginЗатем добавьте оператор выбора:

事务2

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

Как исправить галлюцинации

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

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

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