2021-02-15 10:01:19
Понятия в БД, часть 2. Уровни изоляции
Изоляция в ACID говорит: транзакция должна выполняется так, как будто других транзакций нет.
Единственный надёжный способ - запускать транзакции последовательно. Это медленно, поэтому БД поддерживает менее строгие модели изоляции. База работает быстрее, но возможны аномалии данных.
В этом посте углубимся в детали - что за аномалии, что за уровни изоляции, и какие проблемы они решают.
Все проблемы транзакций давно изучены:
dirty reads, write skews и т.д. Чем больше проблем решает БД, тем сложнее код и тем медленнее работает БД. Уровни изоляции позволяют найти баланс между скоростью и корректностью.
В SQL стандарте их 4:
READ_UNCOMMITED
READ_COMMITED
REPEATABLE_READ
SERIALIZABLE
Каждый уровень гарантирует решение чёткого списка проблем. Для остальных нужно либо писать код, либо забить на возможные неточности.
В стандарте SQL описаны три проблемы:
Dirty reads - грязные чтения
Транзакция 1 обновляет поле Х. Другие транзакции видят новое значения Х до того, как транзакция 1 завершится.
В вопросе с переводом денег как раз возникла такая ситуация. Транзакция перевода ещё не завершилась, а другая транзакция прочитала промежуточные значения.
Где возникает проблема: только на уровне
READ_UNCOMMITED.
Nonrepeatable reads - неповторяющиеся чтения
Транзакция 2 читает поле X и работает с ним. В это время транзакция 3 обновляет поле Х. В итоге транзакция 2 работает с устаревшим значением.
Более формально, "неповторяющееся чтение" - когда чтение одного поля в начале и конце транзакции даёт разные результаты. Но редко кто читает одно поле дважды, на практике получается либо бесполезная транзакция с устаревшими данными, либо несогласованные данные внутри транзакции.
Проблема остро проявляется для долгих запросов - бэкапов или аналитики. Решается на уровне
REPEATABLE_READ и выше.
Фантомные чтения
Транзакция 3 проверяет условие по большому количеству записей. Транзакция 4 меняет выборку, например, добавляет новую запись. Если условие в транзакции 3 перестанет выполнятся, транзакция 3 этого не заметит.
Важно, что условие касается не одного поля, а многих. В этом разница с неповторяющимся чтением - там меняется одно конкретное поле, а в фантомном чтении меняется вся выборка, по которому проверяется условие.
Проблема решается на уровне
SERIALIZABLE.
Подробные примеры и схемы аномалий есть в Википедии. Я перечислила основные, но у многих проблем есть разновидности, поэтому аномалий получается больше, чем уровней изоляции.
Каждая БД сама решает, какие проблемы решать на конкретных уровнях. У MS SQL Server - 5 уровней, у Oracle - 3. Большинство NoSQL баз не поддерживают транзакции, поэтому для них указывать тип изоляции бессмысленно. В универсальных адаптерах типа JDBC, Hibernate и Spring Data уровней столько, сколько в стандарте - 4.
Ещё одна проблема, которой нет в SQL стандарте, но встречается на практике:
Потерянный апдейт
Транзакции работают с одними данными и не учитывают друг друга.
Пример: транзакция 5 и транзакция 6 одновременно прочитали значение счётчика. Каждая транзакция прибавила к значению единицу и обновила поле счётчика. Вначале они прочитали одно значение, и получается, что один инкремент потерялся.
Проблема решается не только уровнями изоляции, но и SQL конструкциями:
Атомарный апдейт:
UPDATE test SET x=x-1 where id=1;
Блокировка строки:
SELECT * FROM test WHERE id = 1 FOR UPDATE;
Итог. Как учитывать внутрянку БД в написании кода:
Выбирать уровень изоляции с учётом вероятности и критичности проблем
Уточнить в документации БД, какие проблемы решает выбранный уровень
Писать код с учётом возможных аномалий
Помнить о потерянных апдейтах
2.3K viewsedited 07:01