Новый триал [сборка 13377].

Привет всем.

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

Немного истории.
Эмуляция вложенных транзакций (а провайдер их именно эмулирует) впервые была реализована в далеком 2005.

Как он работает?
Этот механизм опирается на точки сохранения (savepoints) FB1.5 и IB7.1. То есть, фактически, вложенная транзакция представляет собой точку сохранения, которой управляет IBProvider. Провайдер не позволяет пользователю явно влиять на эти точки сохранения (если он попытается самостоятельно ими управлять) и отслеживает влияние других точек сохранений на собственные savepoint-ы. Кроме того, учитывается различие в реализациях точек сохранений у InterBase и Firebird.

IBProvider, внутри себя,
1. создает вложенную транзакцию через запрос «SAVEPOINT svp_name».
2. откатывает вложенную транзакцию (грубо говоря) посредством запроса «ROLLBACK TO SAVEPOINT svp_name».
3. коммитит вложенную транзакцию (грубо говоря) посредством запроса «RELEASE SAVEPOINT svp_name».

Поддерживаются откаты и коммиты с продолжениями.

А пользователь создает и завершает вложенные транзакции через API компонент доступа. SQL-ные запросы, связанные с транзакциями, влияют только на корневую транзакцию.

Как включается?
По умолчанию, поддержка вложенных транзакций отключена. Включается она через свойство «nested_trans».

Где используется?
Например, при использовании провайдера в качестве связанного сервера MSSQL. Последний стартует вложенные транзакции при модификации данных через связанный сервер.

Что не так в первоначальной реализации?
Как уже было сказано ранее, «вложенные транзакции» это просто альтернативное управление точками сохранений. Однако недавно пришло понимание того, что если уже назвали служебные savepoint-ы «вложенными транзакциями», то надо и управлять ими как транзакциями. То есть более аккуратно.

При освобождении точки сохранения (RELEASE SAVEPOINT), сервер (FB/IB) осуществляет освобождение всех её дочерних точек. В случае Firebird, правда, можно указать ключевое слово ONLY и тогда дочерние точки сохранения останутся действительными. Но суть не в этом. А в существовании неявного завершения точек сохранения. У Interbase, при пересоздании точки сохранения, также освобождаются дочерние точки сохранения (Firebird их не трогает).

Если среди дочерних точек будет служебная точка сохранения вложенной транзакции, то сервер её тоже освободит (ему её «служебность» не указ). А провайдер переведет связанную с этой точкой вложенную транзакцию в состояние «закоммичена».

Еще точки сохранения освобождаются сервером при коммите всей транзакции. Провайдер также отслеживает такие операции и учитывает их влияние на состояния вложенных транзакций.

И что-то мне подсказывает — неявная фиксация транзакций (пусть и эмулируемых) это, определенно, неправильно. Если пользователь явно стартовал транзакцию, то пусть он её явно и завершает. А все операции, которые приводят к неявному завершению транзакций, нужно отрабатывать как ошибки.

Кстати, неявный откат вложенных транзакций, при откате охватывающей транзакции (или точки сохранения) — это вполне нормальное (и ожидаемое) поведение. Я, по крайней мере, не вижу здесь потенциальных проблем.

В итоге, в IBProvider внесены соответствующие изменения, которые (по-умолчанию) запрещают неявные коммиты вложенных транзакций. Далее я приведу код (на VBScript и ADODB) для всех случаев, где провайдер выявляет неявный коммит и генерирует ошибку.

В некоторых примерах используется вспомогательная компонента «LCPI.IBP.Samples.TransactionLevel.1», которая входит в состав всех дистрибутивов провайдера (включая триал). Она предоставляет высокоуровневый доступ к OLEDB объектам управления вложенными транзакциями.

Пример 1. Коммит корневой транзакции через API (Firebird).

option explicit

dim cn
set cn=createobject("ADODB.Connection")

cn.Provider="LCPI.IBProvider.3"

call cn.Open("location=localhost:d:\database\employee.fdb;" _
             +"nested_trans=true", _
             "gamer", _
             "vermut")

call cn.BeginTrans() 'level 1
call cn.BeginTrans() 'level 2

dim tr1
set tr1=createobject("LCPI.IBP.Samples.TransactionLevel.1")

call tr1.Attach(cn,1)

on error resume next

'error
call tr1.Commit(false)

wscript.echo "["&err.Source&"]"&vbCrLf&err.Description

На выходе:
Вывод примера №1.
По стандарту OLEDB, сбой коммита должен завершаться откатом транзакции. Об этом сообщает первый блок сообщения (результат трансляции кода XACT_E_COMMITFAILED). Ну а второй блок — это уже сообщение из IBProvider’а где он, собственно говоря, сообщает о тех же последствиях ошибки коммита.

Пример 2. Коммит вложенной транзакции через API (Firebird).

option explicit

dim cn
set cn=createobject("ADODB.Connection")

cn.Provider="LCPI.IBProvider.3"

call cn.Open("location=localhost:d:\database\employee.fdb;" _
             +"nested_trans=true", _
             "gamer", _
             "vermut")

call cn.BeginTrans() 'level 1
call cn.BeginTrans() 'level 2
call cn.BeginTrans() 'level 3

dim tr1
set tr1=createobject("LCPI.IBP.Samples.TransactionLevel.1")

call tr1.Attach(cn,1)

dim tr2
set tr2=createobject("LCPI.IBP.Samples.TransactionLevel.1")

call tr2.Attach(cn,2)

on error resume next

'error
call tr2.Commit(false)

wscript.echo "["&err.Source&"]"&vbCrLf&err.Description

on error goto 0

wscript.echo
wscript.echo "Commit Level 1"
call tr1.Commit()

На выходе:
Вывод примера №2.
Здесь, также как и в предыдущем примере, провайдер генерирует ошибку и откатывает транзакцию. Однако откатываются только второй и третий уровни. Первый уровень потом фиксируется без каких-либо проблем.

Пример 3. Коммит корневой транзации через SQL (Firebird).

option explicit

dim cn
set cn=createobject("ADODB.Connection")

cn.Provider="LCPI.IBProvider.3"

call cn.Open("location=localhost:d:\database\employee.fdb;" _
             +"nested_trans=true", _
             "gamer", _
             "vermut")

call cn.BeginTrans() 'level 1
call cn.BeginTrans() 'level 2

on error resume next

'error
call cn.Execute("COMMIT")

wscript.echo "["&err.Source&"]"&vbCrLf&err.Description

На выходе:
Вывод примера №3.
В отличии от коммита через API, здесь не производится откат транзакции. Провайдер просто генерирует ошибку.

Пример 4. Освобождение точки сохранения (Firebird).

option explicit

dim cn
set cn=createobject("ADODB.Connection")

cn.Provider="LCPI.IBProvider.3"

call cn.Open("location=localhost:d:\database\employee.fdb;" _
             +"nested_trans=true", _
             "gamer", _
             "vermut")

call cn.BeginTrans() 'level 1

call cn.Execute("SAVEPOINT A")

call cn.BeginTrans() 'level 2

on error resume next

'error
call cn.Execute("RELEASE SAVEPOINT A")

wscript.echo "["&err.Source&"]"&vbCrLf&err.Description

На выходе:
Вывод примера №4.
В данном случае, провайдер сохраняет состояние точки сохранения и её вложенной транзакции (второй уровень).

В случае Firebird-а можно выполнить запрос «RELEASE SAVEPOINT A ONLY». Такой запрос провайдер пропустит. Точка «A» будет освобождена, а транзакция второго уровня останется активной.

Привет 5. Пересоздание точки сохранения (Interbase).

option explicit

dim cn
set cn=createobject("ADODB.Connection")

cn.Provider="LCPI.IBProvider.3"

'use x64 interbase client
call cn.Open _
 ("location=vxpsp2-ib10-0-4:e:\database\employee.gdb;" _
  +"dbclient_library=d:\Users\Dima\IB_FB_YA\IB100\ibclient64.dll;" _
  +"nested_trans=true", _
  "gamer", _
  "vermut")

call cn.BeginTrans() 'level 1

call cn.Execute("SAVEPOINT A")

call cn.BeginTrans() 'level 2

on error resume next

'error
call cn.Execute("SAVEPOINT A")

wscript.echo "["&err.Source&"]"&vbCrLf&err.Description

На выходе:
Вывод примера #5.
Состояние точки сохранения и вложенной транзакции второго уровня не меняется.

Какие могут быть проблемы с этими новыми запретами?
При работе с DTC провайдер не будет генерировать подробные описания ошибок при подготовке и коммите транзакции первого уровня. Он будет молча откатывать транзакции и уведомлять DTC об ошибке.

Верните все назад!
Для тех, у кого вдруг все вдруг поломалось из за этого нововведения, есть откат к предыдущему поведению через новое свойство инициализации — nested_trans_rules

Кстати.
1. Изначально я хотел писать примеры на .NET с использованием нашего «LCPI .Net Data Provider for OLEDB». Однако обнаружилось, что он сам неявно коммитит вложенные транзакции при коммите родительской транзакции. Это поведение было скопировано с микрософтовского .Net провайдера для OLEDB. Надо будет там тоже гайки закрутить 🙂

2. Yaffil тоже поддерживает точки сохранения. IBProvider про это знает и разрешает вложенные транзакции для этого сервера.

Один комментарий

Вести с полей.  on Январь 28th, 2015

[…] коммита «вложенных» транзакций. Добил затеянное два с половиной года назад. Вернуть старое поведение можно через новое […]

Оставить комментарий