You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单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.

解决方法

核心修改点

  1. 移除FDQuery的Transaction属性赋值:FDQuery会自动继承所属Connection的Transaction,手动指定会导致事务分支关联冲突。
  2. 调整事务启动方式:通过第一个连接的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 03:05:21