Подтверждение и откат транзакций Все операции над БД (включая команды DDL) в IB выполняются в контексте какой-либо транзакции. Транзакции могут быть явными и неявными. Неявная транзакция имеет параметры READ WRITE WAIT SNAPSHOT, начинается при выполнении любой команды и продолжается до команды явного завершения транзакции (COMMIT, ROLLBACK). Для выполнения транзакции с другими параметрами, а также для одновременного выполнения нескольких транзакций с одного клиента сервер IB позволяет стартовать явные транзакции.
Для подтверждения транзакции используются команды COMMIT (подтверждение транзакции и ее завершение), ROLLBACK (отказ от изменений и завершение транзакции) и COMMIT RETAINING (подтверждение транзакции с сохранением контекста). Команда ROLLBACK RETAINING должна появиться в IB 6.0
COMMIT RETAINING фиксирует транзакцию, но сразу же после этого стартует новую с теми же параметрами, что и у завершенной транзакции и сохраняя тот же курсор. Таким образом, клиентской программе не требуется заново создавать курсор и выполнять FETCH.
Использование компонентов IB Express
Компонент TIBTransaction
При использовании IB Express все транзакции контролируются компонентом TIBTransaction. Он содержит следующие ключевые свойства:
- Params
- DefaultAction
- IdleTimer
а также методы:
- StartTransaction
- Commit
- CommitRetaining
- Rollback
- RollbackRetaining
Режим, в котором запускается транзакция, указывается в свойстве Params в виде списка символьных строк. Каждая опция режима указывается на отдельной строке, запятые не нужны. Опции транзакции можно подробнее посмотреть в IB API Guide.
Режим доступа:
- isc_tpb_write - READ WRITE
- isc_tpb_read - READ ONLY
Режим ожидания:
- isc_tpb_nowait - NO WAIT
- isc_tpb_wait - WAIT
Уровень изоляции:
- isc_tpb_read_committed, isc_tpb_no_rec_version - READ COMMITTED NO RECORD_VERSION
- isc_tpb_read_committed, isc_tpb_rec_version - READ COMMITTED RECORD_VERSION
- isc_tpb_concurrency - SNAPSHOT
- isc_tpb_consistency - SNAPSHOT TABLE STABILITY
Параметр isc_tpb_version3, как того требует API Guide, указывать не нужно.
Посмотреть, является ли транзакция активной, можно с помощью свойства Active или с помощью метода InTransaction - в обоих случаях вызывается один и тот же метод GetInTransaction.
Действие по умолчанию, выполняемое при завершении транзакции, устанавливается свойством DefaultAction. По умолчанию DefaultAction=TACommit, то есть изменения подтверждаются и курсор закрывается.
Свойство IdleTimer определяет временной интервал в миллисекундах, по истечении которого будет производиться автоматическое завершение транзакции согласно свойству DefaultAction. Для создания таймера используется компонент типа TTimer и при значении IdleTimer=0 действие по умолчанию не выполнится ни разу.
Неявные транзакции в IBX
Если явно не вызывать TIBTransaction.StartTransaction, а просто открыть набор данных, подключенный к TIBTransaction, (например TIBTable) начнется неявная, с точки зрения клиентской программы, транзакция. К сожалению, неявную транзакцию можно только подтвердить (COMMIT), и нельзя откатить (ROLLBACK).
Дело в том, что при открытии набора данных IBX проверяет, активна ли транзакция, ассоциированная с набором данных, и если нет - стартует ее автоматически, выставляя при этом внутренний флаг набора данных FDidActivate:=True. Закрыть набор данных можно двумя способами: вызвать TIBCustomDataSet.Close, что приведет и к закрытию неявной транзакции, либо вызвать напрямую один из методов TIBTransaction, которые, завершив транзакцию, закроют и набор данных (кроме CommitRetaining).
При закрытии набора данных по Close выполняется:
if FDidActivate then DeactivateTransaction;
и далее в методе DeactivateTransaction:
if Transaction.InTransaction then Transaction.Commit;
При завершении транзакции методом TIBTransaction.Commit или TIBTransaction.Rollback происходит следующее. В методе TIBTransaction.EndTransaction выполняются завершающие действия для объектов, ассоциированных с транзакцией:
for i := 0 to FSQLObjects.Count - 1 do if FSQLObjects[i] <> nil then SQLObjects[i].DoBeforeTransactionEnd;
В методе TIBCustomDataSet.DoBeforeTransactionEnd выполняется
if Active then Active := False;
После этого в методе TIBCustomDataSet.InternalClose выполняется закрытие набора данных, описанное выше:
if FDidActivate then DeactivateTransaction;
и далее:
if Transaction.InTransaction then Transaction.Commit;
Таким образом, текущая реализация неявных транзакций такова, что они всегда подтверждаются.
Явные транзакции в IBX
Для более полного управления транзакциями необходимо вызвать TIBTransaction.StartTransaction. Тогда изменения можно не только подтвердить, но и отменить. Commit и Rollback завершают транзакцию и закрывают все ассоциированные с ней наборы данных, а CommitRetaining подтверждает изменения, и стартует новую транзакцию, не изменяя контекста. Побочным эффектом CommitRetaining является то, что набор данных не закрывается и пользователь может продолжать вносить изменения.
Использование таймера и действия по умолчанию
DefaultAction и IdleTimer можно использовать, чтобы периодически подтверждать внесенные изменения и уменьшить вероятность конфликта между транзакциями. Для этого установите IdleTimer в некоторое, не очень большое значение, например 1000 и DefaultAction:=TACommitRetaining. Тогда TIBTransaction будет каждую секунду выполнять COMMIT RETAINING.
Примечание.
- В случае явного управления транзакциями с помощью StartTransaction и Commit/Rollback не забывайте о таймере. Срабатывание действия по таймеру не позволит вручную завершить явную транзакцию.
- В файле bdereadme.txt от BDE 5.1 обтекаемо написано, что "Soft commits are a feature of InterBase that let the driver retain the cursor when committing changes." Человек, незнакомый с понятием Soft Commit (он же COMMIT RETAINING), может подумать, что этот режим подтверждает транзакцию, и оставляет открытый курсор вне контекста транзакции. Так вот, это не так. Все действия с БД происходят в контексте транзакции, поэтому COMMIT RETAINING просто клонирует подтверждаемую транзакцию. Наивно думать (как прежде думал я), что можно стартовать транзакцию, закачать данные с сервера, выполнить COMMIT RETAINING и просматривать их фактически в "оффлайне". При работе с данными посредством data-aware компонентов всегда имеется открытая транзакция.
Так что не стоит выставлять IdleTimer:=1 и DefaultAction:=TACommitRetaining в надежде на быструю "закачку" данных, завершение транзакции и последующий просмотр данных во внутреннем буфере. Все будет гораздо хуже: каждую 1 миллисекунду (на самом деле, несколько реже) программа будет подтверждать изменения на сервере, что приведет к существенному росту сетевого трафика.
Если же DefaultAction имеет значение TACommit или TARollback, то после первого же действия по таймеру транзакция будет завершена.
Использование CachedUpdates
Установка CachedUpdates:=True несколько изменяет идеологию выполнения транзакций. Как и в случае CachedUpdates:=False, транзакция все равно является открытой на протяжении работы с данными через data-aware компоненты, но изменения вносятся только при вызове ApplyUpdates.
В случае CachedUpdates:=False измененные данные сразу отправляется на сервер и, если не происходит конфликта, вызванного другой транзакцией, хранятся там до осуществления COMMIT/ROLLBACK. Таким образом, конфликты могут быть выявлены еще до того, как пользователь захочет сохранить все внесенные изменения на сервере. В случае CachedUpdates:=True все изменения накапливаются в локальном буфере и при ApplyUpdates единым пакетом отправляются на сервер, после чего подтверждаются.
|