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

IBX, FIBPlus, UIB, ADO, .Net и прочее-прочее-прочее, в общем все, что относится к созданию приложений, работающих с InterBase, Firebird и Yaffil - клиент-серверных, трехзвенных, консольных и т.п.

Модератор: kdv

Ответить
СанЕк
Сообщения: 25
Зарегистрирован: 25 окт 2005, 11:45

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

Сообщение СанЕк » 25 окт 2005, 11:55

Имееться функция удаления записей из таблици или иная другая
Например

Код: Выделить всё

Function deleteTable(Var Q: TIBSQL; Const tablename, whereline: String;AutoTransiction:boolean=true): boolean;
Begin
if AutoTransiction then begin // авто стартование транзикции
  if Q.Transaction.Active then Q.Transaction.Rollback;
  Q.Transaction.StartTransaction;
end;

try
 result := true;
 With q.SQL Do
  Begin
// генерация SQL запроса
  End;
 Try
  q.ExecQuery; // выполнение запроса
 Except  result := false;
  IBDataBaseError;
 End;
finally
if result then Q.Transaction.Commit else Q.Transaction.Rollback;
end;
End;
Вопрос в следующем:
Хотелось бы знать, кто что думает об такой структуре запроса, какие еще есть методы гарантированного завершения транзикции, стоит ли обрабатывать транзикции при Select запросах.

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 25 окт 2005, 12:40

1. старайся избегать "автотранзакций".

2. ТАК писать не рекомендую, настоятельно -
Q.Transaction.StartTransaction....

Ты же поименовал компонент transaction? Чего бы прямо не написать
MyTransaction.StartTransaction. А то потом в коде не поймешь, что за транзакция стартовала.

3. блок try/except/finally и в конце commit или rollback - это песня.

тут я даже не знаю, что тебе посоветовать...

4. без нужды звать rollback (в начале кода) - это моветон (неприлично и себе дороже).

Владимир Каратаев
Сообщения: 22
Зарегистрирован: 01 ноя 2004, 11:11

Re: Корректная работа с транзикциями

Сообщение Владимир Каратаев » 25 окт 2005, 12:45

Код: Выделить всё

procedure deleteTable(Var Q: TIBSQL; Const tablename, whereline: String;AutoTransiction:boolean=true);
Begin
try
if AutoTransiction then begin // авто стартование транзикции
  if Q.Transaction.Active then Q.Transaction.Rollback;
  Q.Transaction.StartTransaction;
end;
 With q.SQL Do
  Begin
// генерация SQL запроса
  End;
  q.ExecQuery; // выполнение запроса
finally
if result then Q.Transaction.Commit else Q.Transaction.Rollback;
end;
End;
1.если ты генерируешь exception, то нет необходимости во флаге успешности выполнения запроса. по-любому, если процедура (потому я и предлагаю вариант с процедурой, а не функцией) выполнилась, то произойдет нормальный выход из нее, иначе- exception.
2.блок finally позволяет откатить изменения в любом случае.
3.обрати внимание, у компонента TIBTransaction есть свойство AllowAutoStart. Если его поставить =true, транзакция будет сама стартовать. может тебе такой вариант подойдет.

если нужен вариант с функцией:

Код: Выделить всё

function deleteTable(Var Q: TIBSQL; Const tablename, whereline: String;AutoTransiction:boolean=true): boolean;
Begin
  result:=false;
  try
    try
      if AutoTransiction then begin // авто стартование транзикции
        if Q.Transaction.Active then Q.Transaction.Rollback;
        Q.Transaction.StartTransaction;
     end;
     With q.SQL Do Begin
// генерация SQL запроса
     End;
     q.ExecQuery; // выполнение запроса
     Result:=true;
   finally
     if result then Q.Transaction.Commit else Q.Transaction.Rollback;
   end;
  except
    IBDataBaseError; 
  end;
End;
ps: прислушайся к советам kdv. в твоем случае они могут и не пригодиться, но на ус мотай. 8)

СанЕк
Сообщения: 25
Зарегистрирован: 25 окт 2005, 11:45

Re: Корректная работа с транзикциями

Сообщение СанЕк » 25 окт 2005, 13:03

Код: Выделить всё

Q.Transaction.StartTransaction;
Используеться только потому что функция как бы универсальная и использует ту транзикцию которая присоединена к TIBSQL.

Код: Выделить всё

if Q.Transaction.Active then Q.Transaction.Rollback;
на тот случай если вдруг каким нить образом произошел выход из какой либо процедуры(обновления/добавления/удаления) без завершения транзикции, то есть Commit не произошел, и что бы не выдавалось сообщение о том что транзикция уже стартанула я ее Rollback. Но насчет этого я еще подумаю. Наверное стоит ее убрать всетаки. Пусть юзеры прогу перегружают в таком случае.

А функция это для того что бы можно было самому управлять транзикциями на крайний случай.

Всем спасибо за ценные советы.

Dimitry Sibiryakov
Заслуженный разработчик
Сообщения: 1436
Зарегистрирован: 15 сен 2005, 09:05

Сообщение Dimitry Sibiryakov » 25 окт 2005, 14:07

Кстати, commit тоже может обломаться, так что я рекомендую такой код:

Код: Выделить всё

Transaction.StartTransaction;
try
  ...
  Transaction.Commit;
except
  Transaction.Rollback;
  raise;
end;

Владимир Каратаев
Сообщения: 22
Зарегистрирован: 01 ноя 2004, 11:11

Сообщение Владимир Каратаев » 25 окт 2005, 14:19

Dimitry Sibiryakov писал(а):Кстати, commit тоже может обломаться, так что я рекомендую такой код:

Код: Выделить всё

Transaction.StartTransaction;
try
  ...
  Transaction.Commit;
except
  Transaction.Rollback;
  raise;
end;
3-й совет kdv- тут Rollback может обломиться. в except лучше вообще не делать ничего, что вызовет еще один except.

Dimitry Sibiryakov
Заслуженный разработчик
Сообщения: 1436
Зарегистрирован: 15 сен 2005, 09:05

Сообщение Dimitry Sibiryakov » 25 окт 2005, 15:18

У Rollback не так уж много шансов для ошибки. Фактически, три:
1) Shared transaction handle
2) Transaction handle == null
3) Проблемы с коннектом при использовании 2PC (только).

СанЕк
Сообщения: 25
Зарегистрирован: 25 окт 2005, 11:45

Сообщение СанЕк » 25 окт 2005, 16:08

Совместными усилиями получаем функцию или процедуру, кому что больше нравиться, с почти 100% завершением рабочей транзикции :!:

Код: Выделить всё

Function deleteTable(. . . .): boolean;
Begin
if AutoTransiction then Q.Transaction.StartTransaction;
try
 result := true;
 // генерируем запрос
 Try
  q.ExecQuery;// выполняем
 Except
  result := false; // запрос обломился
  IBDataBaseError;
 End;
finally
// если все ОК пробуем вызвать Commit
if result then try Q.Transaction.Commit Except result:=false;end;
// если Commit обломился то 
if not result then try Q.Transaction.Rollback;finally end;
end;
End;
Всем спасибо!! :lol:
Кому если нужно, могу скинуть модуль с несколькими функциями для динамического создания и выполнения запросов Insert, Update, Delete для Ib/FB. Собственно часть его только что здесь откорректирована :lol: [/code]

Владимир Каратаев
Сообщения: 22
Зарегистрирован: 01 ноя 2004, 11:11

Сообщение Владимир Каратаев » 25 окт 2005, 16:39

[quote="СанЕк"]Совместными усилиями получаем функцию или процедуру, кому что больше нравиться, с почти 100% завершением рабочей транзикции :!:

Код: Выделить всё

Function deleteTable(. . . .): boolean;
Begin
if AutoTransiction then Q.Transaction.StartTransaction;
try
 result := true;
 // генерируем запрос
 Try
  q.ExecQuery;// выполняем
 Except
  result := false; // запрос обломился
  IBDataBaseError;
 End;
finally
// если все ОК пробуем вызвать Commit
if result then try Q.Transaction.Commit Except result:=false;end;
// если Commit обломился то 
if not result then try Q.Transaction.Rollback;finally end;
end;
End;
Эх, Санек, нифига ты так и не понял... :cry: Написал самую сбойную функцию, какую можно придумать. я ж тебе выше дал готовый правильный вариант функции. используй ее. Так будет еще правильнее:

Код: Выделить всё

function deleteTable(Var Q: TIBSQL; Const tablename, whereline: String;AutoTransiction:boolean=true): boolean;
Begin
  result:=false;
  try
    try
      if AutoTransiction then begin // авто стартование транзикции
        if Q.Transaction.Active then Q.Transaction.Rollback;
        Q.Transaction.StartTransaction;
     end;
     With q.SQL Do Begin
// генерация SQL запроса
     End;
     q.ExecQuery; // выполнение запроса
   finally
     if result then Q.Transaction.Commit else Q.Transaction.Rollback;
   end;
   Result:=true;
  except
    IBDataBaseError;
  end;
End;

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 25 окт 2005, 17:15

я бы еще упростил, объясняю почему:

startTransaction не имеет смысла обрамлять try/except/finally, потому что если она НЕ стартанула, то один фиг дальше в коде процедуры делать нечего, и ошибка спокойно может уйти "наверх", если ее, конечно, не надо обрабатывать по месту (чего мы тут в упор не видим).

то есть

Код: Выделить всё

StartTransaction
try
  ExecQuery
except
   rollback
end
Commit;
и то, если в execSQL один-единственный оператор (и это не вызов селективной процедуры), то спокойно можно написать

Код: Выделить всё

StartTransaction
try
  ExecQuery
finally
  Commit
end;
это здесь уже было в каком то топике....

СанЕк
Сообщения: 25
Зарегистрирован: 25 окт 2005, 11:45

Сообщение СанЕк » 26 окт 2005, 11:35

Тут у меня еще такое мнение есть по поводу

Код: Выделить всё

StartTransaction 
try 
  ExecQuery 
except 
   rollback 
end 
Commit;
В этом случае при возникновении ошибки все ОК, но в программе выскочит сообщение и выполнение процедуры (если я не ошибаюсь), в которой была вызвана функция с этим кодом остановиться, например в обработчике FormClose вызывает DeleteTable - возникает ошибка и получаеться что окно нельзя закрыть. Поэтому я всетаки придерживаюсь такого кода

Код: Выделить всё

StartTransaction 
try
 try 
   ExecQuery 
 except 
    rollback 
 result:=false // говорит о том что произошла ошибка
 end 
finally
 Commit;
 result:=true // все тип топ
end;

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 26 окт 2005, 11:42

например в обработчике FormClose вызывает DeleteTable
ужас какой. я не знаю, что ты там и как пишешь, но по отрывочным впечатлениям это, извини, какой то кошмар. У тебя явные проблемы с алгоритмикой.

СанЕк
Сообщения: 25
Зарегистрирован: 25 окт 2005, 11:45

Сообщение СанЕк » 26 окт 2005, 11:53

:D Ну этож пример, с алгоритмикой проблем у меня нет :D, просто сложно все на пальцах обяснять. пока вроде все работает, и работает хорошо, а это главное.

Владимир Каратаев
Сообщения: 22
Зарегистрирован: 01 ноя 2004, 11:11

Сообщение Владимир Каратаев » 26 окт 2005, 11:54

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

Код: Выделить всё

StartTransaction 
try
 try 
   ExecQuery 
 except 
    rollback 
 result:=false // говорит о том что произошла ошибка
 end 
finally
 Commit;
 result:=true // все тип топ
end;
еще раз повторяю, как будет правильно:

Код: Выделить всё

function deleteTable(Var Q: TIBSQL; Const tablename, whereline: String;AutoTransiction:boolean=true): boolean;
Begin
  result:=false;
  try
    if AutoTransiction then begin // авто стартование транзикции
      if Q.Transaction.Active then Q.Transaction.Rollback;
      Q.Transaction.StartTransaction;
    end;
    try
     With q.SQL Do Begin
// генерация SQL запроса
     End;
     q.ExecQuery; // выполнение запроса
     Result:=true;
   finally
     if result then Q.Transaction.Commit else Q.Transaction.Rollback;
   end;
  except
    Result:=false;
    IBDataBaseError;
  end;
End;
твой вариант кода- полная бредятина. функция у тебя всегда будет возращать true, даже в случае ошибки, а делфи при компиляции тебе напишет предупреждение, что результат функции не определен. у меня пальцы устанут набирать текст- сколько ты потенциальных глюков посадил в свой код. поэтому говорю последний раз- бери тот код, который я тебе уже третий раз даю. это готовый рабочий код. неужели так трудно его просто скопировать к себе и использовать? :evil:

Ответить