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

多任务应用中FDQuery与连接池的正确使用方法及访问冲突解决

问题修复方案

核心错误原因

你遇到的访问冲突主要来自三类不符合规范的实现:

  • 多线程无锁访问VCL控件、共享日志对象、共享FDManager操作
  • 连接池配置缺失必要参数,默认配置不符合多线程场景要求
  • 实现逻辑不符合FireDAC官方多线程使用规范

具体修改步骤

1. 新增线程安全锁保护共享资源

首先在窗体私有变量中添加临界区,所有跨线程共享的资源访问都需要加锁:

private
  FCritical: TCriticalSection;
  FTargetTime: string; // 存储目标时间,避免线程直接访问VCL控件

在FormCreate中初始化临界区:

procedure TForm2.FormCreate(Sender: TObject);
begin
  FCritical := TCriticalSection.Create;
  Log1 := TStringList.Create;
  Log2 := TStringList.Create;
  FDManager := TFDManager.Create(nil);
  FDManager.ResourceOptions.AutoReconnect := True;
  FDriverMySQL := TFDPhysMySQLDriverLink.Create(nil);
  FDriverSQLite := TFDPhysSQLiteDriverLink.Create(nil);
  // 提前初始化连接池,避免线程中并发创建
  CreatePooledConnection;
end;

在FormClose中释放临界区:

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  FCritical.Free;
  Log1.Free;
  Log2.Free;
  FDManager.Free;
  FDriverMySQL.Free;
  FDriverSQLite.Free;
end;

2. 补充连接池必要配置

修改CreatePooledConnection方法,补充连接池核心参数,同时加锁避免并发创建冲突:

procedure TForm2.CreatePooledConnection;
var
  oParams: TStrings;
  Conn:TStrings;
  Server, Database, UserName, Password: String;
  Port: Integer;
begin
  // 填写实际数据库参数
  Server   := '你的数据库地址';
  Database := '你的数据库名';
  UserName := '你的用户名';
  Password := '你的密码';
  Port     := 3306;

  FCritical.Enter;
  try
    oParams := TStringList.Create;
    Conn    := TStringList.Create;
    try
      FDManager.GetConnectionDefNames(Conn);
      if Conn.IndexOf('Connection') = -1 then
      begin
        oParams.Add('Server='+Server);
        oParams.Add('Database='+Database);
        oParams.Add('User_Name='+UserName);
        oParams.Add('Password='+Password);
        oParams.Add('Port='+IntToStr(Port));
        oParams.Add('DriverName='+FDriverMySQL.Name);
        // 连接池核心配置
        oParams.Add('Pooled=True');
        oParams.Add('Pool_MaximumItems=20'); // 最大连接数,可根据并发需求调整
        oParams.Add('Pool_MinimumItems=2'); // 最小空闲连接数
        oParams.Add('Pool_IdleTimeout=30000'); // 空闲连接30秒自动释放
        oParams.Add('ConnectionLifeTime=300'); // 连接生命周期300秒,避免长连接失效
        FDManager.AddConnectionDef('Connection', 'MySQL', oParams, True);
        FDManager.Open;
      end;
    finally
      oParams.Free;
      Conn.Free;
    end;
  finally
    FCritical.Leave;
  end;
end;

3. 禁止跨线程直接访问VCL控件

修改BitBtn1Click,提前在主线程读取目标时间存储到变量,避免子线程访问VCL控件:

procedure TForm2.BitBtn1Click(Sender: TObject);
begin
  // 主线程读取目标时间
  FCritical.Enter;
  try
    FTargetTime := MaskEdit1.Text;
  finally
    FCritical.Leave;
  end;
  Ts1 := TTask.Create(procedure
                      begin
                        Test1;
                      end);
  Ts1.Start;
  Ts2 := TTask.Create(procedure
                      begin
                        Test2;
                      end);
  Ts2.Start;
end;

线程逻辑中判断时间改为读取FTargetTime变量,所有日志操作加锁:

procedure TForm2.Test1;
var
  fQuery : TFDQuery;
  HoraS: String;
begin
  HoraS := FormatDateTime('hh:nn', TimeOf(NOW));
  while HoraS <> FTargetTime do
  begin
    Sleep(10000);
    HoraS := FormatDateTime('hh:nn', TimeOf(NOW));
  end;
  try
    FCritical.Enter;
    try
      Log1.Add('Creating TFDQuery');
    finally
      FCritical.Leave;
    end;
    fQuery := CreateQuery;
    try
      FCritical.Enter;
      try
        Log1.Add('Adding SQL in TFDQuery');
      finally
        FCritical.Leave;
      end;
      fQuery.SQL.Clear;
      fQuery.SQL.Add('SELECT id FROM table_two');
      fQuery.SQL.Add('WHERE delete_flag IS NULL');
      fQuery.SQL.Add('LIMIT 1');
      FCritical.Enter;
      try
        Log1.Add('SQL of TFDQuery: ' + fQuery.SQL.Text);
        Log1.Add('Opening TFDQuery');
      finally
        FCritical.Leave;
      end;
      fQuery.Open;
      FCritical.Enter;
      try
        Log1.Add('Result of TFDQuery: ' + fQuery.FieldByName('id').AsString);
      finally
        FCritical.Leave;
      end;
    finally
      fQuery.Free;
    end;
  except
    on E:Exception do
    begin
      FCritical.Enter;
      try
        Log1.Add('Error: ' + E.Message);
      finally
        FCritical.Leave;
      end;
    end;
  end;
end;

Test2方法做相同的修改即可。

4. 优化TFDQuery创建逻辑

修改CreateQuery方法,添加连接自动释放配置,确保查询关闭后连接自动归还到连接池:

function TForm2.CreateQuery: TFDQuery;
begin
  Result := TFDQuery.Create(nil);
  Result.FetchOptions.Mode := fmAll;
  Result.FetchOptions.RecordCountMode := cmFetched;
  Result.ResourceOptions.AutoReconnect := True;
  Result.ConnectionOptions.AutoClose := True; // 查询关闭后自动释放连接回池
  Result.ConnectionName := 'Connection';
end;

额外注意

  • 不要跨线程共享任何FireDAC的数据集、连接对象,每个线程的数据库对象要在线程内创建、线程内释放
  • 示例中BitBtn2Click里Ts1.Cancel;;多了一个冗余分号,可直接删除避免编译警告

内容的提问来源于stack exchange,提问作者Diego_F

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 18:36:02