单FDTransaction管控双FireDAC连接与FDQuery触发[FireDAC][Phys][FB]-343错误
FireDAC实现Firebird多数据库事务的问题解决
问题背景
原有Delphi XE6项目使用IBX组件,通过单个IBTransaction管控两个Firebird 2.5数据库,事务提交/回滚时两个库的修改会同步执行。Firebird原生支持多数据库事务。
改用FireDAC组件并手动管理事务(已禁用AutoStart、AutoStop、AutoCommit)时,执行第二个FDQuery的Prepare方法触发错误:
[FireDAC][Phys][FB]-343. Cannot set default transaction
测试代码如下:
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Phys.IBBase, FireDAC.Phys.FB, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client, FireDAC.VCLUI.Wait, FireDAC.Comp.UI; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); procedure FDTranBeforeCommit(Sender: TObject); procedure FDTranBeforeRollback(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var FDGUIxWaitCur: TFDGUIxWaitCursor; FDPhysFBDriv: TFDPhysFBDriverLink; FDCon1: TFDConnection; FDCon2: TFDConnection; FDTrx: TFDTransaction; FDQry1: TFDQuery; FDQry2: TFDQuery; begin FDGUIxWaitCur := TFDGUIxWaitCursor.Create(nil); FDPhysFBDriv := TFDPhysFBDriverLink.Create(nil); FDPhysFBDriv.VendorLib := 'C:\Program Files\Firebird\Firebird_2_5\WOW64\fbclient.dll'; { 32 bit applications on 64 bit Firebird } FDCon1 := TFDConnection.Create(nil); FDCon2 := TFDConnection.Create(nil); FDTrx := TFDTransaction.Create(nil); FDQry1 := TFDQuery.Create(nil); FDQry2 := TFDQuery.Create(nil); try try FDTrx.Options.AutoCommit := False; FDTrx.Options.AutoStart := False; FDTrx.Options.AutoStop := False; FDTrx.BeforeCommit := FDTranBeforeCommit; FDTrx.BeforeRollback := FDTranBeforeRollback; FDCon1.TxOptions.AutoCommit := False; FDCon1.TxOptions.AutoStart := False; FDCon1.TxOptions.AutoStop := False; FDCon1.Params.Clear; FDCon1.Params.Add('DriverID=FB'); FDCon1.Params.Add('Database=c:\a\dbone.fdb'); FDCon1.Params.Add('Server=LOCALHOST/3050'); FDCon1.Params.Add('Protocol=TCPIP'); FDCon1.Params.Add('user_name=NAME'); FDCon1.Params.Add('password=passw'); FDCon1.Params.Add('lc_ctype=WIN1250'); FDCon2.TxOptions.AutoCommit := False; FDCon2.TxOptions.AutoStart := False; FDCon2.TxOptions.AutoStop := False; FDCon2.Params.Clear; FDCon2.Params.Add('DriverID=FB'); FDCon2.Params.Add('Database=c:\a\dbtwo.fdb'); FDCon2.Params.Add('Server=LOCALHOST/3050'); FDCon2.Params.Add('Protocol=TCPIP'); FDCon2.Params.Add('user_name=NAME'); FDCon2.Params.Add('password=passw'); FDCon2.Params.Add('lc_ctype=WIN1250'); FDCon1.Transaction := FDTrx; FDCon2.Transaction := FDTrx; FDQry1.Connection := FDCon1; FDQry1.Transaction := FDTrx; FDQry2.Connection := FDCon2; FDQry2.Transaction := FDTrx; FDCon1.Open; FDCon2.Open; if FDTrx.Active then ShowMessage('trx active'); if not FDTrx.Active then FDTrx.StartTransaction; if FDTrx.Active then ShowMessage('trx active'); FDQry2.Close; FDQry2.SQL.Text := 'select count(id) from table'; FDQry2.Prepare; FDQry2.Open; ShowMessage(Format('DBTwo table records count %d',[FDQry2.Fields[0].AsInteger])); FDQry2.Close; if FDTrx.Active then ShowMessage('trx active'); FDQry1.Close; FDQry1.SQL.Text := 'select count(id) from table'; FDQry1.Prepare; { An error appears here } FDQry1.Open; ShowMessage(Format('DBOne table records count %d',[FDQry1.Fields[0].AsInteger])); FDQry1.Close; if FDTrx.Active then FDTrx.Commit; except on E: Exception do begin ShowMessage(e.Message); if FDTrx.Active then FDTrx.Rollback; end; end; finally FDQry2.Close; FDQry1.Close; FDCon2.Close; FDCon1.Close; FDQry2.Free; FDQry1.Free; FDTrx.Free; FDCon2.Free; FDCon1.Free; FDPhysFBDriv.Free; FDGUIxWaitCur.Free; end; end; procedure TForm1.FDTranBeforeCommit(Sender: TObject); begin ShowMessage('Trx BeforeCommit'); end; procedure TForm1.FDTranBeforeRollback(Sender: TObject); begin ShowMessage('Trx BeforeRollback'); end; end.
解决方法
核心修改点
- 移除FDQuery的Transaction属性赋值:FDQuery会自动继承所属Connection的Transaction,手动指定会导致事务分支关联冲突。
- 调整事务启动方式:通过第一个连接的
StartTransaction方法启动事务,第二个连接会自动加入现有事务分支,而非直接调用TFDTransaction的StartTransaction。
修改后的完整代码
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Stan.Param, FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Phys.IBBase, FireDAC.Phys.FB, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client, FireDAC.VCLUI.Wait, FireDAC.Comp.UI; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); procedure FDTranBeforeCommit(Sender: TObject); procedure FDTranBeforeRollback(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var FDGUIxWaitCur: TFDGUIxWaitCursor; FDPhysFBDriv: TFDPhysFBDriverLink; FDCon1: TFDConnection; FDCon2: TFDConnection; FDTrx: TFDTransaction; FDQry1: TFDQuery; FDQry2: TFDQuery; begin FDGUIxWaitCur := TFDGUIxWaitCursor.Create(nil); FDPhysFBDriv := TFDPhysFBDriverLink.Create(nil); FDPhysFBDriv.VendorLib := 'C:\Program Files\Firebird\Firebird_2_5\WOW64\fbclient.dll'; { 32 bit applications on 64 bit Firebird } FDCon1 := TFDConnection.Create(nil); FDCon2 := TFDConnection.Create(nil); FDTrx := TFDTransaction.Create(nil); FDQry1 := TFDQuery.Create(nil); FDQry2 := TFDQuery.Create(nil); try try FDTrx.Options.AutoCommit := False; FDTrx.Options.AutoStart := False; FDTrx.Options.AutoStop := False; FDTrx.BeforeCommit := FDTranBeforeCommit; FDTrx.BeforeRollback := FDTranBeforeRollback; FDCon1.TxOptions.AutoCommit := False; FDCon1.TxOptions.AutoStart := False; FDCon1.TxOptions.AutoStop := False; FDCon1.Params.Clear; FDCon1.Params.Add('DriverID=FB'); FDCon1.Params.Add('Database=c:\a\dbone.fdb'); FDCon1.Params.Add('Server=LOCALHOST/3050'); FDCon1.Params.Add('Protocol=TCPIP'); FDCon1.Params.Add('user_name=NAME'); FDCon1.Params.Add('password=passw'); FDCon1.Params.Add('lc_ctype=WIN1250'); FDCon2.TxOptions.AutoCommit := False; FDCon2.TxOptions.AutoStart := False; FDCon2.TxOptions.AutoStop := False; FDCon2.Params.Clear; FDCon2.Params.Add('DriverID=FB'); FDCon2.Params.Add('Database=c:\a\dbtwo.fdb'); FDCon2.Params.Add('Server=LOCALHOST/3050'); FDCon2.Params.Add('Protocol=TCPIP'); FDCon2.Params.Add('user_name=NAME'); FDCon2.Params.Add('password=passw'); FDCon2.Params.Add('lc_ctype=WIN1250'); FDCon1.Transaction := FDTrx; FDCon2.Transaction := FDTrx; // 移除FDQuery的Transaction赋值,自动继承Connection的事务 FDQry1.Connection := FDCon1; FDQry2.Connection := FDCon2; FDCon1.Open; FDCon2.Open; // 通过第一个连接启动事务,第二个连接自动加入分支 if not FDTrx.Active then FDCon1.StartTransaction; if FDTrx.Active then ShowMessage('trx active'); FDQry2.Close; FDQry2.SQL.Text := 'select count(id) from table'; FDQry2.Prepare; FDQry2.Open; ShowMessage(Format('DBTwo table records count %d',[FDQry2.Fields[0].AsInteger])); FDQry2.Close; if FDTrx.Active then ShowMessage('trx active'); FDQry1.Close; FDQry1.SQL.Text := 'select count(id) from table'; FDQry1.Prepare; FDQry1.Open; ShowMessage(Format('DBOne table records count %d',[FDQry1.Fields[0].AsInteger])); FDQry1.Close; if FDTrx.Active then FDTrx.Commit; except on E: Exception do begin ShowMessage(e.Message); if FDTrx.Active then FDTrx.Rollback; end; end; finally FDQry2.Close; FDQry1.Close; FDCon2.Close; FDCon1.Close; FDQry2.Free; FDQry1.Free; FDTrx.Free; FDCon2.Free; FDCon1.Free; FDPhysFBDriv.Free; FDGUIxWaitCur.Free; end; end; procedure TForm1.FDTranBeforeCommit(Sender: TObject); begin ShowMessage('Trx BeforeCommit'); end; procedure TForm1.FDTranBeforeRollback(Sender: TObject); begin ShowMessage('Trx BeforeRollback'); end; end.
原理说明
Firebird的跨库事务依赖共享的事务上下文,FireDAC中单个TFDTransaction可关联多个连接,但需通过第一个连接启动事务,后续连接会自动注册到该事务分支。手动为FDQuery指定Transaction会破坏FireDAC的事务分支关联逻辑,导致默认事务设置失败的错误。
内容的提问来源于stack exchange,提问作者KarelH
相关产品推荐
相关产品推荐

