Поддержка транзакций (свойства, уровни изолированности)
В многопользовательских системах с одной базой данных одновременно могут работать несколько пользователей или прикладных программ. Предельной задачей системы является обеспечение изолированности пользователей, т.е. создание достоверной и надежной иллюзии того, что каждый из пользователей работает с БД в одиночку.
В связи со свойством сохранения целостности БД транзакции являются подходящими единицами изолированности пользователей. Действительно, если с каждым сеансом работы с базой данных ассоциируется транзакция, то каждый пользователь начинает работу с согласованным состоянием базы данных, т.е. с таким состоянием, в котором база данных могла бы находиться, даже если бы пользователь работал с ней в одиночку.
Существуют некоторые свойства, которыми должна обладать любая транзакция.
Атомарность – это свойство типа «все или ничего». Любая транзакция представляет собой неделимую единицу работы, которая может быть либо выполнена вся целиком, либо не выполнена вовсе.
Согласованность. Каждая транзакция должна переводить базу данных из одного согласованного состояния в другое согласованное состояние.
Изолированность. Все транзакции выполняются независимо одна от другой. То есть промежуточные результаты незавершенной транзакции не должны быть доступны другим транзакциям.
Продолжительность. Результаты успешно завершенной транзакции должны сохранятся в базе данных постоянно и не должны быть утеряны в результате последующих сбоев.
Важнейшей функцией БД является организация доступа многих пользователей к общим данным, используемым совместно. Обеспечить параллельный доступ относительно несложно, если все пользователи будут только читать данные, помещенные в базу. В этом случае работа каждого из них не оказывает никакого влияния на работу остальных пользователей. Однако, если пользователи будут изменять данные, то возможно взаимное влияние процессов друг на друга, способное привести к несогласованности данных.
При соблюдении обязательного требования поддержания целостности базы данных возможны следующие уровни изолированности транзакций:
Первый уровень - отсутствие потерянных изменений. Например, транзакция 1 изменяет объект базы данных A (увеличивает значение поля равного 50 на 100). До завершения транзакции 1 транзакция 2 также изменяет объект A (уменьшает значение того же поля равного 50 на 10). После выполнения обеих транзакций значение поля должно быть равно 140. Транзакции начинаются практически одновременно и каждая из них считывает значение 50. Затем транзакция 1 увеличивает значение на 100 и записывает результат равный значению 150. Далее транзакция 2 уменьшает свою копию значения 50 на 10 и записывает результат, перекрывая результат предыдущего обновления. В результате значение поля становится равно 40 вместо 140. Чтобы избежать такой ситуации необходимо запретить чтение исходного значения до завершения транзакции 1. Отсутствие потерянных изменений является минимальным требованием к СУБД по части синхронизации параллельно выполняемых транзакций.
Второй уровень - отсутствие чтения "грязных данных". Например, транзакция 1 изменяет объект базы данных A. Параллельно с этим транзакция 2 читает объект A. Поскольку операция изменения еще не завершена, транзакция 2 видит несогласованные "грязные" данные, существующие до выполнения транзакции 1. В результате внесение изменений транзакцией 2, после завершения транзакции 1 может быть отвернуто из-за нарушения немедленно проверяемых ограничений целостности. Чтобы избежать ситуации чтения "грязных" данных, до завершения транзакции 1, изменяющей объект A, никакая другая транзакция не должна читать объект A.
Третий уровень - отсутствие неповторяющихся чтений. Рассмотрим следующий пример. Транзакция 1 читает объект базы данных A. До завершения транзакции 1 транзакция 2 изменяет объект A и успешно завершается оператором COMMIT. Транзакция 1 повторно читает объект A и видит его измененное состояние. Чтобы избежать неповторяющихся чтений, до завершения транзакции 1 никакая другая транзакция не должна изменять объект A. В большинстве систем это является максимальным требованием к синхронизации транзакций.
Блокировка – процедура, используемая для управления параллельным доступом к данным. Когда некоторая транзакция получает доступ к базе данных, механизм блокировки позволяет (с целью исключения получения некорректных результатов) отклонить попытки получения доступа к этим же данным со стороны других транзакций. Существуют несколько различных вариантов этого механизма, однако все они построены на одном и том же фундаментальном принципе: транзакция должна потребовать блокировку для чтения или для записи некоторого элемента данных перед тем, как она сможет выполнить соответствующую операцию.
Поскольку операция чтения не может служить причиной конфликта, допускается устанавливать блокировку для чтения одного и того же элемента одновременно несколькими транзакциями. Блокировка элемента для записи предоставляет транзакции эксклюзивное право доступа к нему. Следовательно, до тех пор пока транзакция будет удерживать некоторый элемент заблокированным для записи, ни одна другая транзакция не сможет ни считать, ни обновить его.
Механизм блокировок разрешает проблемы, связанные с доступом нескольких пользователей (программ) к одним и тем же данным. Однако его применение связано с существенным замедлением обработки транзакций, вызванным необходимостью ожидания, когда освободятся данные, захваченные конкурирующей транзакцией. Можно попытаться минимизировать вызванный этим ущерб, локализуя фрагменты данных, захватываемые транзакцией. Так, СУБД может блокировать всю базу данных целиком (очевидно, что это неприемлемый вариант), таблицу базы данных, часть таблицы, отдельную строку. Современные СУБД используют в большинстве блокировки на уровне частей таблиц (страниц) и/или на уровне записей.
При блокировке на уровне страниц СУБД захватывает для выполнения транзакции некоторый фрагмент таблицы, запрещая доступ к нему (на время выполнения транзакции) конкурирующим транзакциям. Последние, могут захватить другие страницы той же таблицы. Так как размер страниц обычно невелик (2-4 Кб), то время ожидания транзакций, конкурирующих за доступ к страницам таблицы, оказывается приемлемым даже для режима оперативного доступа к базе данных.
Если СУБД реализована таким образом, что может захватывать для выполнения транзакции отдельные строки таблицы, то скорость обработки транзакции существенно повышается. Блокировка на уровне записей (строк) позволяет добиться максимальной производительности за счет того, что захватываемый объект (запись) является минимальной структурной единицей базы данных.
Транзакции могут попасть в тупиковую ситуацию - состояние неразрешимой взаимоблокировки. Оно иллюстрируется простейшей ситуацией. Транзакция A обновляет таблицу Заказ, блокируя некоторую ее страницу (для определенности - страницу C). В то же время транзакция B обновляет таблицу Товар, блокируя страницу D таблицы. Далее, транзакция A пытается обновить данные на странице D таблицы Товар (но, поскольку транзакция B еще не завершена, транзакция A переводится в состояние ожидания того момента, когда транзакция B освободит захваченную ею страницу). В этот же момент транзакция B пытается обновить данные на странице C таблицы Заказ. Сделать этого она не может, потому что страница захвачена транзакцией A и не освобождена, так как последняя находится в состоянии ожидания. Транзакция B также переводится в состояния ожидания. Налицо ситуация взаимоблокировки транзакций, которая может продолжаться бесконечно, если СУБД не предпримет специальные меры.
Для их предотвращения СУБД периодически проверяет блокировки, установленные активными транзакциями. Если СУБД обнаруживает взаимоблокировки, она выбирает одну из транзакций, вызвавшую ситуацию взаимоблокировки, и прерывает ее. Это освобождает данные для внесения изменений конкурирующей транзакцией, разрешая тупиковую ситуацию. Программа, которая инициировала прерванную транзакцию, получает сообщение об ошибке.
