Опубликован: 07.05.2010 | Уровень: специалист | Доступ: платный
Лекция 24:

Транзакции

< Лекция 23 || Лекция 24: 12 || Лекция 25 >
Аннотация: На этой лекции мы подробно разберем суть понятия "транзакция", узнаем, как InterBase обеспечивает работу транзакций, познакомимся с многоверсионной архитектурой, изучим уровни изолированности транзакций. На практическом примере создадим приложение, работающее сразу с двумя транзакциями.

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

В "Введение в клиент-серверные БД. InterBase" мы упоминали, что транзакции - это пакет запросов, который последовательно производит изменения БД и либо принимается, если все изменения записи подтверждены, либо отвергается, если хоть один запрос завершился неуспешно. Запросы могут состоять из операторов SELECT / INSERT / UPDATE / DELETE, причем в контексте одной транзакции может быть как один такой запрос, так и множество запросов. Однако понятие " транзакция " гораздо глубже этого короткого определения.

Транзакции запускаются на стороне сервера, причем стартуют они только по приказу клиентского приложения. Завершают работу они также по приказу клиентского приложения, причем при успешном завершении транзакция подтверждается, а при неудаче - отвергается. В триггерах или процедурах вызвать старт транзакции невозможно.

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

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

Допустим, наша БД работает без транзакций, и нам нужно произвести упомянутый перевод. Тут мы можем поступить двумя разными способами:

  1. Вначале снимаем деньги с одного счета, затем добавляем их к другому счету.
  2. Вначале добавляем деньги к другому счету, затем снимаем их с первого счета.

Теперь предположим, что в середине этой операции произошел какой-то сбой: отключился сервер БД, например. В первом случае деньги будут потеряны - они ушли с одного счета, но не дошли до другого. Во втором случае деньги "размножатся" - появятся на втором счету, но при этом останутся и на первом. И в том, и в другом случае произойдет нарушение целостности БД - данные станут недостоверны.

Однако все SQL -серверы баз данных работают с применением транзакций. Еще говорят, что все изменения базы данных происходят в контексте одной или нескольких транзакций. InterBase не исключение, более того, InterBase предоставляет гораздо более гибкие инструменты для управления транзакциями, чем многие другие SQL -серверы. Если произошел какой то сбой при переводе денег, то транзакция не получила подтверждения, а база данных осталась в прежнем состоянии - целостность и достоверность БД не нарушились.

Более 20 лет назад исследователи Тео Хендер и Андреас Рютер опубликовали обзор, в котором описывали принципы поддержания целостности БД в много-клиентской среде. Эти принципы принято называть ACID ( Atomicity, Consistency , Isolation, Durability - Атомарность, Согласованность, Изоляция и Устойчивость ). Все транзакции действуют по этим принципам.

Атомарность (Atomicity)

Атомарность подразумевает, что транзакция является единицей работы с базой данных. Внутри транзакции может происходить множество модификаций БД, однако транзакция действует по принципу "все или ничего". Когда транзакция подтверждается ( Commit ), то подтверждаются все изменения данных, сделанные в ее контексте. Когда она отвергается (откатывается, Rollback ), то отвергаются и все изменения. В случае возникновения сбоя, система, восстанавливаясь, ликвидирует последствия транзакций, не успевших завершиться.

Согласованность (Consistency)

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

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

Изолированность (Isolation)

В базе данных может выполняться множество транзакций одновременно. Бывает, что две, и более транзакции пытаются изменить одну и ту же запись. Чтобы гарантировать целостность данных, транзакции выполняются изолированно друг от друга. Можно сказать, что каждая транзакция работает со своей копией (версией) данных. Существует несколько степеней изолированности транзакций, о чем далее мы поговорим подробней.

Устойчивость (Durability)

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

Неявный и явный старт транзакций

Все действия над базой данных, совершаемые в клиентском приложении, происходят внутри (в контексте) транзакции. В примерах предыдущих лекций мы соединяли клиентские приложения с базой данных, вообще не используя никаких транзакций. Однако это не значит, что их не было. Просто транзакции запускались неявно, автоматически. Причем с параметрами, созданными Delphi "по умолчанию". В серьезных приложениях БД это недопустимо, так как может привести к многочисленным конфликтам.

Транзакцию можно стартовать и явно. Из стандартных механизмов доступа мы будем использовать, в основном, InterBase Express (IBX). В приложении должен присутствовать как минимум, один компонент IBTransaction. С помощью этого компонента можно явно указать параметры транзакции, управлять стартом, подтверждением или откатом транзакции. Делается это с помощью следующих методов компонента:

  • StartTransaction - Старт транзакции.
  • Commit - Подтверждение транзакции с последующим ее закрытием.
  • CommitRetaining - Подтверждение транзакции без ее закрытия.
  • Rollback - Откат транзакции с последующим ее закрытием.
  • RollbackRetaining - Откат транзакции без ее закрытия.

Впрочем, компоненты доступа к данным неявно производят запуск транзакции, поэтому StartTransaction обычно пропускают. А вот подтверждение или откат транзакции проверяют, как правило, в блоке try…except в клиентском приложении:

try
   //какие то действия над данными...
   IBTransaction1.Commit; //подтверждаем
Except
   //произошла ошибка
   ShowMessage('Невозможно выполнить операцию!');
   IBTransaction1.Rollback;  //делаем откат транзакции
end; //try

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

Как транзакция работает

В базе данных имеется специальная область, которая называется TIP ( Transaction Inventory Page - Инвентарная Страница Транзакций). При старте транзакции, ей присваивается идентификатор ( TID, Transaction ID ) - инвентарный номер, который сохраняется в TIP. При этом у самой последней транзакции будет наибольший идентификатор. В TIP, помимо номера стартовавшей транзакции, сохраняется и ее состояние, которое может быть Active (В работе), Committed (Подтвержденная), Rolled Back (Отмененная, откат) и In Limbo (Неопределенная).

Активной называется транзакция, которая в настоящий момент выполняется.

Подтвержденной называется транзакция, которая успешно завершила свою работу, как правило, по команде Commit.

Отмененной называют транзакцию, которая завершилась неудачно. При этом производится откат сделанных ей действий, как правило, командой RollBack.

Неопределенной транзакцией ( Limbo ) называют транзакцию, которая работает одновременно с двумя или более базами данных. При завершении такой транзакции, InterBase совершает двухфазное подтверждение Commit, гарантируя, что изменения будут внесены либо во все БД, либо ни в одну. При этом подтверждения в базах данных будут даваться по очереди. Если в это время возникнет сбой системы, то может получиться, что в каких то БД изменения были сделаны, а в каких то нет. При этом транзакция переходит в неопределенное состояние, когда сервер не знает, следует ли подтвердить эту транзакцию, или откатить.

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

Если транзакция подтвердилась, InterBase попытается пометить предыдущую оригинальную запись, как удаленную, а версию завершенной транзакции - как оригинал. Когда InterBase сохраняет изменения, то в новую запись помещается и идентификатор транзакции, которая внесла эти изменения (любая строка таблицы содержит идентификатор создавшей ее транзакции ).

Если транзакция завершилась неуспешно, оригинал записи так и остается оригиналом.

Если же транзакция только читает запись, не пытаясь ее изменить, то для нее не создается собственной версии.

Однако могут возникать и конфликты транзакций. Предположим, что стартовала транзакция Т1. Она создала версию записи, и поменяла ее данные. В это время стартовала конкурирующая транзакция Т2, и создала версию той же записи. Поскольку Т1 еще не завершилась, Т2 при старте не могла видеть изменения данных, сделанные Т1, а значит, создала свою версию из старого оригинала. Теперь Т1 завершает работу по Commit. Как должен поступить InterBase? Если он пометит версию записи Т1 как оригинал, а старую запись, как удаленную, то в версии Т2 окажутся ложные данные! Действия InterBase в этом случае будут зависеть от параметров этих транзакций, о чем ниже мы поговорим подробней.

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

Таким образом, говорят, что InterBase имеет многоверсионную архитектуру ( MGA - Multi Generation Architecture ). Такая архитектура позволяет организовать работу с базой данных так, чтобы читающие пользователи не блокировали пишущих. Кроме того, при возникновении сбоев в системе, InterBase очень быстро восстанавливается, благодаря именно MGA. Кстати, InterBase является первым SQL -сервером, который поддерживает многоверсионную архитектуру.

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

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

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

В InterBase присутствует механизм удаления старых версий, который запускается новыми транзакциями. Новая транзакция, запрашивая запись, считывает все версии этой записи. При этом делается проверка на то, была ли транзакция, сделавшая эту версию, отменена ( RollBack ) или подтверждена ( Commit ). Если транзакция была отменена, значит, эта версия - мусор, который следует удалить. Если же имеется несколько версий, сделанных подтвержденными транзакциями, то актуальной считается версия с наибольшим идентификатором транзакций. Остальные версии считаются устаревшими и также подлежат удалению.

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

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

  • Активная транзакция - транзакция, которая стартовала, но еще не завершена.
  • Заинтересованная транзакция - это транзакция, конкурирующая с текущей транзакцией.
  • Старейшая активная транзакция - это такая активная транзакция, которая стартовала раньше других. Или иначе, это активная транзакция с наименьшим идентификатором.
  • Старейшая заинтересованная транзакция - это такая заинтересованная транзакция, которая стартовала раньше других. Или иначе, это заинтересованная транзакция с наименьшим идентификатором.

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

< Лекция 23 || Лекция 24: 12 || Лекция 25 >
Евгений Медведев
Евгений Медведев

В лекции №2 вставляю модуль данных. При попытке заменить name на  fDM выдает ошибку: "The project already contains a form or module named fDM!". Что делать? 

Анна Зеленина
Анна Зеленина

При вводе типов успешно сохраняется только 1я строчка. При попытке ввести второй тип вылезает сообщение об ошибке "project mymenu.exe raised exception class EOleException with message 'Microsoft Драйвер ODBC Paradox В операции должен использоваться обновляемый запрос'. 

Денис Попов
Денис Попов
Россия, Оренбург, Оренбургский государственный университет, 2015
Рустам Кадыров
Рустам Кадыров
Россия, Тирлян, Тирлянская школа №5, 2003