Транзакция - (англ. transaction) — группа последовательных операций с базой данных, которая представляет собой логическую единицу работы с данными. Транзакция может быть выполнена либо целиком и успешно, соблюдая целостность данных и независимо от параллельно идущих других транзакций, либо не выполнена вообще и тогда она не должна произвести никакого эффекта. Транзакция переводит базу данных из одного непротиворечивого состояния в другое.
При успешном завершении транзакция фиксируется, выполняется операция «commit». При возникновении сбоя уже выполненные действия отменяются и транзакция «откатывается», выполняется операция «rollback».
Cвойства, которыми должны обладать транзакции и поддерживающая их система. Обычно для обозначения этих свойств используют аббревиатуру ACID – Atomicity, Consistency, Isolation, Durability. В переводе на русский – Атомарность, Согласованность, Изолированность и Долговечность. Рассмотрим эти свойства подробнее.
Атомарность как раз и означает неделимость транзакции и требование либо выполнять ее целиком, либо не выполнять вообще.
Согласованность – согласованность означает еще и соответствие базы данных бизнес-правилам. В частности, из ограничений целостности может следовать запрет на отрицательное значение остатка на счете, но, скажем, требования равенства списываемой и зачисляемой суммы при переводе средств со счета на счет – это уже бизнес-правило, которое при помощи ограничений целостности не выразить.
Изолированность – это свойство транзакции выполняться независимо друг от друга. То есть, в процессе выполнения одной транзакции другие не могут повлиять на результат ее выполнения. Обеспечение совершенной независимости транзакций друг от друга требует и соответствующих ресурсов, и отрицательно сказывается на скорости выполнения операций с данными, так что существует несколько, так называемых, уровней изолированности, которые будут рассмотрены далее.
Долговечность – это свойство транзакции сохраняться в системе «навсегда» после успешного завершения. Очевидно, что в процессе работы СУБД могут возникать сбои оборудования, отключения питания, иные ошибки, но гарантируется, что если выдано сообщение об успешном завершении транзакции, то изменения, внесенные ей в данные, сохранятся в любом случае и когда состояние базы данных будет восстановлено после сбоя, эти изменения никуда не денутся.
Восстановление. Если пользователь не задействует транзакции в явном виде, то предполагается, что каждая операция – это отдельная транзакция. Таким образом, работа каждого пользовательского приложения – это цепочка выполненных транзакций. При наличии нескольких пользователей (или при использовании многопоточных приложений) цепочек транзакций может быть несколько.
СУБД ведет так называемый журнал транзакций, который хранится на диске. В этот журнал вносятся записи о выполняемых системой действиях, перед тем, как действительно внести изменения в данные, хранящиеся на диске. В журнале хранятся и данные об исходном состоянии записей. Именно эти данные используются в случае отмены транзакции для восстановления исходного состояния базы данных.
Параллельность. Проблемы параллельных транзакций и уровни изолированности. В
Организация параллельного доступа к данным достаточно нетривиальная задача. Рассмотрим проблемы, которые могут возникнуть, если одновременный доступ к данным осуществляется произвольно.
- Проблема последнего изменения. Возникает, когда две транзакции пытаются изменить одни и те же данные.
- Проблема «грязного» чтения. Возникает в ситуациях, когда одна транзакция считывает данные, измененные другой, еще не зафиксированной транзакцией. Соответственно, в случае отката второй транзакции у первой останутся неверные данные.
- Проблема неповторяемого чтения. Возникает, когда одна транзакция неоднократно считывает данные, а другие транзакции их в это время меняют.
- Проблема чтения «фантомов». Возникает, когда транзакция считывает данные несколько раз, а другая (или другие) транзакции их в это время добавляют. В итоге, результаты более поздних считываний будут содержать дополнительные данные, «фантомы», которые соответствуют условиям отбора, но ранее не выбирались.
Для того, чтобы иметь возможность получить приемлемую производительность и минимизировать возможные проблемы были введены уровни изолированности. Их четыре, и они пронумерованы от 0 – самый низкий уровень, до 3 – наивысший.
0 – read uncommitted. На этом уровне транзакции изолируются слабее всего. На этом уровне возможно и «грязное» чтение, и неповторяющееся чтение, и чтение «фантомов». Гарантируется только отсутствие потерянных обновлений – одновременный доступ нескольких транзакций к одним и тем же данным для их изменения исключается.
1 – read committed. Этот уровень, как следует из его названия, запрещает транзакциям читать незафиксированные результаты других транзакций. За счет этого исключаются проблемы «грязного» чтения.
2 – repeatable read. Название этого уровня, «повторяемое чтение» говорит о том, что на нем также исключается проблема неповторяемого чтения. То есть, считанные одной транзакцией данные защищаются от изменения или удаления другими транзакциями.
3 – serializable. Это наивысший уровень изолированности, на русский его название можно перевести как «Упорядочиваемый», хотя встречается и вариант «сериализуемый». На этом уровне СУБД выполняет транзакции в таком порядке, чтобы результат их параллельного выполнения был эквивалентен последовательному выполнению по одной. Такой уровень исключает все проблемы одновременного доступа к данным.
Блокировки данных
Для того, чтобы обеспечить изолированность транзакций большинстве СУБД используется механизм блокировок. Перед тем, как начать обрабатывать данные, транзакция устанавливает блокировку, и тем самым защищает данные от нежелательных изменений. Существует две основных блокировки – блокировка чтения и блокировка записи. Также их называют S-блокировка (от Shared, разделяемая) и X-блокировка (от eXclusive – исключительная). Перед тем, как считать необходимые данные, транзакция должна установить блокировку чтения. Блокировка чтения, будучи установленной, не препятствует установке другими транзакциями блокировки чтения, но запрещает установку блокировки записи. Так исключаются ситуации неповторяемого чтения. Если транзакции требуется изменить какие-то данные, то она должна установить на них блокировку записи. Эта блокировка запрещает установление любых других блокировок, так что другие транзакции не смогут ни прочитать, ни тем более изменить те же данные. Если транзакция, пытаясь выполнить одну из операций, сталкивается с блокировкой, то она переходит в режим ожидания, ее выполнение приостанавливается до тех пор, пока не освободятся нужные данные.
Для обнаружения взаимоблокировок можно использовать разные подходы. Во многих СУБД этот вопрос решается через механизм тайм-аутов. Для каждой транзакции определяется время, за которое она должна выполниться, и если она в него не укладывается, то происходит ее откат. Получается, что даже если возникает взаимоблокировка, она устанавливается на некоторый небольшой промежуток времени, а затем автоматически разрешается отменой одной из транзакций.