Грязное чтение и фантомное чтение, это нелегко понять

Java MySQL

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

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

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

 select @@tx_isolation;

image.png

image.pngMySQL содержит 4 уровня изоляции, которые, конечно же, изолируют данные. Чтобы изменить уровень изоляции, вы можете использовать следующую инструкцию SQL.

set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;

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

CREATE TABLE `xjjdog_tx` (
	`id` INT(11) NOT NULL,
	`name` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',
	`money` BIGINT(20) NOT NULL DEFAULT '0',
	PRIMARY KEY (`id`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
INSERT INTO `xjjdog_tx` (`id`, `name`, `money`) VALUES (2, 'xjjdog1', 100);
INSERT INTO `xjjdog_tx` (`id`, `name`, `money`) VALUES (1, 'xjjdog0', 100);

1. Грязные чтения

Грязное чтение означает чтение грязных данных. Что такое грязные данные? Это данные, которые еще не были зафиксированы другой транзакцией. существуетread uncommittedГрязные чтения происходят на уровне изоляции. Например, следующая последовательность

事务 A:set session transaction isolation level read uncommitted;
事务 B:set session transaction isolation level read uncommitted;
事务 A:START TRANSACTION ;
事务 B:START TRANSACTION ;
事务 A:UPDATE xjjdog_tx SET money=money+100 WHERE NAME='xjjdog0';
事务 B:UPDATE xjjdog_tx SET money=money+100 WHERE NAME='xjjdog0';
事务 A:ROLLBACK ;
事务 B:COMMIT ;
事务 B:SELECT * FROM xjjdog_tx ;

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

image.png

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

  1. В сценарии с высокой степенью параллелизма перед началом и завершением транзакции A другая транзакция участвует в чтении и записи строк данных, задействованных в транзакции A.
  2. Уровень изоляции транзакций находится на самом низком незафиксированном чтении.
  3. После того, как вы используете данные, транзакция A откатывается, в результате чего данные, которые вы получили раньше, больше не существуют.

Решение состоит в том, чтобы установить уровень изоляции наread uncommittedвысоко.

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

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

ноread commitedПроисходит неповторяемое чтение.

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

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

Позвольте мне рассказать вам историю.

Жило-было персиковое дерево, на котором росло 12 персиков. Есть обезьяна по имени xjjdog, она хочет съесть на ней персики, но персики еще не созрели.

Когда я пошел посмотреть на него на следующий день, он обнаружил, что одного персика не хватает, и их стало 11. После тщательных расспросов выяснилось, что Обезьяна А съела сначала один.

Когда я на следующий день пошла его смотреть, на один персик стало меньше, и их стало 10. Оказалось, что один съела жадная обезьяна Б.

Таким образом, персики уменьшаются день ото дня, и остаются только два последних, но персики еще не созрели.

Если ты не соберешь персики, то их уже не будет xjjdog сорвал последние 2 персика и хотел их съесть, но выскочила обезьяна X и сказала, что я уже год пяллюсь на эти персики...

В этой истории продолжительность транзакции обезьян A и B равна1天; Продолжительность транзакции xjjdog до桃子成熟; Monkey X имеет большую продолжительность, возможно一年. Персики, которые они видят каждый день, не всегда бывают 12. Сегодняшние персики могут быть съедены другими обезьянами (транзакциями), что приведет к другим наблюдениям Это концепция неповторяемого чтения.

Иногда даже чтение одного и того же значения не доказывает, что все в порядке. Например, финансовая компания присвоила 200 млн для инвестирования в акции, а затем в конце месяца вернула 200 млн. Хотя конечная сумма одинакова, из-за вашего длительного цикла сверки вы не можете найти эту разницу.

Как решить неповторяющееся чтение? Во-первых, давайте посмотрим, является ли неповторяющееся чтение проблемой.

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

Очень хорошо, что xjjdog стоит под персиковым деревом. Когда другие обезьяны захотят сорвать персики, отгоните их. Этот способ работает, но очень неэффективен в базе данных, т.к.serializableпрактика на уровне.

MySQL имеет уровень изоляции транзакций по умолчанию, который называетсяrepeatable read, используя более легкий метод MVCC (innodb).

3. Повторяемое чтение

Это заслуга MVCC (Multi-Version Concurrency Control), которая имеет три характеристики.

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

MVCCсуществуетInnoDBОсновная цель реализации состоит в том, чтобы улучшить одновременную производительность базы данных и лучше справиться с конфликтом чтения-записи, чтобы даже при наличии конфликта чтения-записи можно было достичь неблокировки и неблокировки. Блокировка одновременного чтения. Также есть три ключевые технологии для его реализации:

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

Ключ ко всему есть快照выше этих двух слов.

Например, транзакция A выполняет транзакцию над записью快照读, затем в快照读В этот момент аRead View. В этот момент транзакции B и C еще не зафиксированы, транзакции D и E, до того момента, когда установлен ReadView, фиксация завершена, то это Read View не может читать модификации B и C.

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

В следующем случае читается 500.

Транзакция А Транзакция Б
открытая транзакция открытая транзакция
Количество запросов чтения моментальных снимков (без последствий) составляет 500. Количество запросов на чтение снимка: 500
Сумма обновления 400
совершить транзакцию
select 快照读Сумма 500
select lock in share mode当前读Сумма 400

Следующий случай читает 400.

Транзакция А Транзакция Б
открытая транзакция открытая транзакция
Количество запросов чтения моментальных снимков (без последствий) составляет 500.
Сумма обновления 400
совершить транзакцию
select 快照读Сумма 400
select lock in share mode当前读Сумма 400

(Таблица из блога [SnailMann]).

4. Фантомное чтение

Фантомное чтение, само слово очень психоделично. На уровнях RU, RC и RR будут происходить фантомные чтения.

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

Во-первых, подтвердите текущий повторяемый уровень чтения. Если нет, измените его.

SELECT @@tx_isolation
# set session transaction isolation level repeatable read

Давайте посмотрим на этот чудесный процесс.

image.png

Есть 5 шагов, я отметил их для вас. Представьте их по одному ниже.

  1. Транзакция А использует начало для открытия транзакции, а затем запрашивает запись с идентификатором 3, которая в данный момент не существует. Однако, поскольку чтение моментального снимка открывает представление для чтения для записи с идентификатором 3, запись с идентификатором 3 не может быть прочитана с начала до конца этой транзакции. Отлично, это все, что нам нужно для неповторяющихся чтений.
  2. Затем транзакция B вставляет запись с идентификатором 3 и успешно ее отправляет.
  3. Транзакция A также хочет вставить эту запись в это время, поэтому выполняется та же операция вставки, и база данных сообщает об ошибке, показывая, что эта запись уже существует.
  4. Транзакция A запуталась и хочет узнать, что это за запись, но когда она снова выполняет оператор select, она не может найти эту запись.
  5. Но в других транзакциях эту запись можно увидеть, потому что она была зафиксирована правильно.

Это фантомное чтение.

5. Как избавиться от галлюцинаций

Фантом читает неправильно? В большинстве случаев это правда, но ошибка немного странная. Для предотвращения фантомного чтения необходимо включитьFOR UPDATEТакой высокопрочный замок редко используется на практике.

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

  1. Чтение моментального снимка: обычная операция выбора, которая считывает данные из представления для чтения и считывает可能исторические данные
  2. Текущее чтение: такие операции, как вставка, обновление, удаление, выбор... для обновления, всегда читать последние текущие данные

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

SELECT * FROM xjjdog_tx WHERE id=3 FOR UPDATE

При этом он создаст блокировку, даже если запись с идентификатором 3 не существует (возможно, добавление блокировок строки X или пробелов блокировки следующего ключа в зависимости от того, существует ли запись или нет).

6. Резюме

Ниже приводится краткое изложение.

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

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

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

Чтобы решить фантомное чтение, нужно добавить блокировки (X-lock, Gap-lock и т.д.), например для обновления, все изменения в текущем чтении до конца транзакции, естественно проблем нет.

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