Вступление
В стандарте SQL описывается четыре уровня изоляции транзакций — Read uncommited (Чтение незафиксированных данных), Read committed (Чтение зафиксированных данных), Repeatable read (Повторяемое чтение) и Serializable (Сериализуемость). В данной статье будет рассмотрен жизненный цикл четырёх параллельно выполняющихся транзакций с уровнями изоляции Read committed и Serializable.
Для уровня изоляции Read committed допустимы следующие особые условия чтения данных:
Неповторяемое чтение — транзакция повторно читает те же данные, что и раньше, и обнаруживает, что они были изменены другой транзакцией (которая завершилась после первого чтения).
Фантомное чтение — транзакция повторно выполняет запрос, возвращающий набор строк для некоторого условия, и обнаруживает, что набор строк, удовлетворяющих условию, изменился из-за транзакции, завершившейся за это время.
Что же касается Serializable, то данный уровень изоляции самый строгий, и не имеет феноменов чтения данных.
ACID или 4 свойства транзакций
Прежде чем приступим к рассмотрению уровней изоляции транзакции в паре слов вспомним об основных требованиях к транзакционной системе.
Atomicity (атомарность) — выражается в том, что транзакция должна быть выполнена в целом или не выполнена вовсе.
Consistency (согласованность) — гарантирует, что по мере выполнения транзакций, данные переходят из одного согласованного состояния в другое, то есть транзакция не может разрушить взаимной согласованности данных.
Isolation (изолированность) — локализация пользовательских процессов означает, что конкурирующие за доступ к БД транзакции физически обрабатываются последовательно, изолированно друг от друга, но для пользователей это выглядит, как будто они выполняются параллельно.
Durability (долговечность) — устойчивость к ошибкам — если транзакция завершена успешно, то те изменения в данных, которые были ею произведены, не могут быть потеряны ни при каких обстоятельствах.
Уровень изоляции Read Committed
По умолчанию в PostgreSQL уровень изоляции Read Committed. Такой уровень изоляции всегда позволяет видеть изменения внесённые успешно завершёнными транзакциями в оставшихся параллельно открытых транзакциях. В транзакции, работающей на этом уровне, запрос SELECT (без предложения FOR UPDATE/SHARE) видит только те данные, которые были зафиксированы до начала запроса; он никогда не увидит незафиксированных данных или изменений, внесённых в процессе выполнения запроса параллельными транзакциями. По сути запрос SELECT видит снимок базы данных в момент начала выполнения запроса. Однако SELECT видит результаты изменений, внесённых ранее в этой же транзакции, даже если они ещё не зафиксированы. Также заметьте, что два последовательных оператора SELECT могут видеть разные данные даже в рамках одной транзакции, если какие-то другие транзакции зафиксируют изменения после выполнения первого SELECT.
Суть уровня изоляции Read Committed показана на диаграмме 1.
Примечание: В таблице уже находится запись с первой версией данных (v1). Прошу воспринимать команды SELECT v1; — как команду возвращающую данные версии v1, а UPDATE v1 to v2; — как команду обновления данных с первой версии до второй.
Примечание. На диаграмме не показано действие запроса INSERT. В рамках данного уровня изоляции, строки добавленные, например в шаге 3, в Первой транзакции, были бы ВИДНЫ остальным транзакциям после завершения Первой транзакции.
Частичная изоляция транзакций, обеспечиваемая в режиме Read Committed, приемлема для множества приложений. Этот режим быстр и прост в использовании, однако он подходит не для всех случаев. Приложениям, выполняющим сложные запросы и изменения, могут потребоваться более строго согласованное представление данных, например Serializable.
Уровень изоляции Serializable
Изоляция уровня Serializable обеспечивает беспрепятственный доступ к базе данных транзакциям с SELECT запросами. Но для транзакций с запросами UPDATE и DELETE, уровень изоляции Serializable не допускает модификации одной и той же строки в рамках разных транзакций. При изоляции такого уровня все транзакции обрабатываются так, как будто они все запущены последовательно (одна за другой). Если две одновременные транзакции попытаются обновить одну и туже строку, то это будет не возможно. В таком случае PostgreSQL принудит транзакцию, вторую, да и все последующие, что пытались изменить строку к отмене (откату — ROLLBACK).
Суть уровня изоляции Serializable показана на диаграмме 2.
Примечание. На диаграмме не показано действие запроса INSERT. В рамках данного уровня изоляции, строки добавленные, например в шаге 3, в Первой транзакции, были бы НЕ ДОСТУПНЫ Второй, Третьей и Четвёртой транзакциям после завершения Первой транзакции. Также на диаграмме не показан результат ROLLBACK (Шаги 8 и 11). В случае если бы Вторая и Третья транзакции делали какие либо изменения над не заблокированными данными, то все эти изменения не были бы зафиксированы, так как транзакции завершаются неудачно (суть свойства — Atomicity).
Уровень изоляции Serializable гарантирует, что все затронутые в транзакции данные не будут изменены другими транзакциями. На этом уровне появление "фантомов" исключается, поэтому становятся возможными сложные конкурентные операции. На практике такой уровень изоляции требуется в учетных системах.
Для транзакций содержащих только SELECT запросы, использование уровня изоляции Serializable оправдывает себя тогда, когда вы не хотите видеть внесённые изменения параллельно завершёнными транзакциями в ходе работы текущей транзакции.
Аномалия сериализации (Потерянное обновление)
Ещё один феномен чтения данных, описывается тем, что результат успешной фиксации группы транзакций оказывается несогласованным при всевозможных вариантах исполнения этих транзакций по очереди.
Сориентируйте, пожалуйста, меня в комментариях, если я заблуждаюсь насчёт того, что аномалия сериализации и потерянное обновление связанные между собой феномены.
Документация на сайте PostgreSQL PRO пишет, что Read Committed допускает «Serialization Anomaly». Отечественная Wikipedia, не настаивая на то, что таблица относится именно к PostgreSQL, пишет, что Read Commited предотвращает аномалию сериализации. Английская Википедиа о таком феномене чтения данных умалчивает. Но немецкая Википедия приводит в своей версии таблицы феномен «Lost Updates», указывая на то, что Read Committed может быть не подвержен потере обновлений с дополнительной защитой через курсор (Cursor Stability). Украинская Википедия поддерживает русскоязычную версию статьи, испанская Википедия поддерживает английскую версию статьи. Англоязычная документация по PostgreSQL не отличается от документации с сайта PostgreSQL PRO.
Cursor Stability расширяет блокировочное поведение уровня READ COMMITED для SQL-курсоров, добавляя новую операцию чтения (Fetch) по курсору rc (означает read cursor, т.е. чтение по курсору) и требует, чтобы блокировка устанавливалась на текущем элементе курсора. Блокировка удерживается до тех пор, пока курсор не будет перемещен (пока не измениться его текущий элемент) или закрыт операцией фиксации. Естественно, транзакция, читающая по курсору, может изменить текущую строку (wc – запись по курсору), и в этом случае блокировка по записи этой строки будет сохраняться до тех пор, пока транзакция не зафиксируется, даже после передвижения курсора с последующей выборкой следующей строки.
Итог: 149.
Заключение
Понимание уровней изоляции транзакций является важным аспектом при обработке данных в любой многопользовательской СУБД. Уровни изоляции обладают четко определенными характеристиками и поведением. Более высокие уровни изоляции уменьшают возможности параллельной обработки данных и повышают риск взаимной блокировки процессов. Поэтому корректное использование уровней в зависимости от задач приложений всегда является выбором разработчика в зависимости от требований к обеспечению логической целостности данных, к скорости и к возможности параллельной многопользовательской обработки.
Источники
» MVCC Unmasked
» 13.2. Изоляция транзакций
» Критика уровней изолированности
» Уровни изоляции транзакций в SQL. Шпаргалка
» Уровень изолированности транзакций
» … the lost update phenomena