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

使用ZeosLib时并发数据库调用异常的问题排查与解决

ZeosLib线程安全问题及解决方案

问题根源

ZeosLib的TZConnection(即你代码中的MyConn)不具备线程安全性,尤其是你使用的6.5.1 alpha版本属于早期测试版,线程安全相关的实现存在缺陷。当多个线程共享同一个连接对象时,会引发以下冲突:

  • 多线程并发操作事务(StartTransaction/Commit)导致事务状态混乱
  • 多个查询共用同一连接的执行上下文,造成SQL语句、结果集互相干扰
  • 连接内部状态被并发修改,触发不可预测的错误

这就是你顺序调用正常、并发调用出错的核心原因。

解决方案

1. 使用ZeosLib连接池(推荐)

ZeosLib从早期版本开始就支持连接池功能(6.5.1 alpha已具备基础实现),连接池可以复用连接实例,避免频繁创建/销毁连接的开销,同时保证每个线程使用独立的连接:

  • 配置连接池:将TZConnection的EnablePooling设为True,按需设置MaxConnections(最大连接数)等参数
  • 线程中通过连接池获取可用连接,使用完毕后归还

修改后的示例代码:

procedure TestFunction;
var
  Conn: TZConnection;
  Query: TZEOSQuery;
begin
  // 从连接池获取可用连接(需根据ZeosLib 6.5.1的API实现,比如通过TZConnectionPool管理)
  Conn := GetPooledConnection();
  try
    Query := TZEOSQuery.Create(nil);
    try
      Query.Connection := Conn;
      
      Conn.StartTransaction;
      Query.SQL.Text := 'SELECT * FROM project';
      Query.Open;
      
      while not Query.EOF do
        Query.Next;
      
      Conn.Commit;
    finally
      Query.Close;
      Query.Free;
    end;
  finally
    // 将连接归还至连接池
    ReleasePooledConnection(Conn);
  end;
end;

注:具体连接池API需参考ZeosLib 6.5.1的文档,不同版本实现略有差异。

2. 为每个线程分配独立连接

如果连接池功能不可用,可通过线程本地存储为每个线程创建专属连接,线程结束时统一销毁:

// 全局函数:获取当前线程的专属连接
function GetThreadConnection: TZConnection;
begin
  Result := TZConnection(TThread.ThreadLocalStorage.GetValue(0));
  if not Assigned(Result) then
  begin
    Result := TZConnection.Create(nil);
    // 复制原有连接的配置参数
    Result.Protocol := MyConn.Protocol;
    Result.Database := MyConn.Database;
    Result.User := MyConn.User;
    Result.Password := MyConn.Password;
    Result.Connect;
    TThread.ThreadLocalStorage.SetValue(0, Result);
  end;
end;

// 全局函数:释放当前线程的专属连接
procedure FreeThreadConnection;
var
  Conn: TZConnection;
begin
  Conn := TZConnection(TThread.ThreadLocalStorage.GetValue(0));
  if Assigned(Conn) then
  begin
    Conn.Disconnect;
    Conn.Free;
    TThread.ThreadLocalStorage.SetValue(0, nil);
  end;
end;

procedure TestFunction;
var
  Conn: TZConnection;
  Query: TZEOSQuery;
begin
  Conn := GetThreadConnection;
  try
    Query := TZEOSQuery.Create(nil);
    try
      Query.Connection := Conn;
      
      Conn.StartTransaction;
      Query.SQL.Text := 'SELECT * FROM project';
      Query.Open;
      
      while not Query.EOF do
        Query.Next;
      
      Conn.Commit;
    finally
      Query.Close;
      Query.Free;
    end;
  finally
    // 无需立即释放,在线程OnTerminate事件中调用FreeThreadConnection即可
  end;
end;

3. 对共享连接加锁(不推荐)

如果必须使用单个连接,可通过临界区强制串行化操作,但会完全丧失并发优势,仅适合极低并发场景:

var
  ConnLock: TCriticalSection;

// 程序启动时初始化临界区:ConnLock := TCriticalSection.Create;
// 程序退出时销毁:ConnLock.Free;

procedure TestFunction;
var
  Query: TZEOSQuery;
begin
  ConnLock.Enter;
  try
    Query := TZEOSQuery.Create(nil);
    try
      Query.Connection := MyConn;
      
      MyConn.StartTransaction;
      Query.SQL.Text := 'SELECT * FROM project';
      Query.Open;
      
      while not Query.EOF do
        Query.Next;
      
      MyConn.Commit;
    finally
      Query.Close;
      Query.Free;
    end;
  finally
    ConnLock.Leave;
  end;
end;

额外建议

你使用的ZeosLib 6.5.1 alpha版本过于老旧且为测试版,建议升级到稳定版(如ZeosLib 7.x或8.x),新版本在线程安全、连接池稳定性等方面有大幅改进,能更好地适配Delphi 12和Firebird环境。

内容的提问来源于stack exchange,提问作者SALAH EDDINE FRIG

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 05:02:30