.NET SqlSugar多线程下SqlSugarClient 的线程安全陷阱

使用SqlSugar读取Sqlite数据库,项目运行过程中间歇性抛出以下异常:

SqlSugar.SqlSugarException:"中文提示 : 连接数据库过程中发生错误,检查服务器是否正常连接字符串是否正确,错误信息:Connection was closed, statement was terminatedDbType="Sqlite";ConfigId="".
English Message : Connection open error . Connection was closed, statement was terminatedDbType="Sqlite";ConfigId="" "

经定位,异常抛出位置在一个很普通的查询操作上:

复制代码
public List<SqliteDiskEntity> GetDisksByPDiskId(string env, string project, int pDiskId)
{
    return _db.Queryable<SqliteDiskEntity>().Where(x => x.PDiskId == pDiskId).ToList();
}

连接字符串没有问题,数据库文件也正常存在,且异常并非每次必现,而是偶发性的。

排查过程

  1. 排除连接字符串问题

连接配置如下,看起来没有明显问题:

复制代码
1 private static SqlSugarClient CreateClient(string dbPath)
2 {
3     return new SqlSugarClient(new ConnectionConfig
4     {
5         ConnectionString = $"Data Source={dbPath};Version=3;Journal Mode=Wal;BusyTimeout=5000;",
6         DbType = SqlSugar.DbType.Sqlite,
7         IsAutoCloseConnection = true,
8     });
9 }

已开启 WAL 模式、设置了 BusyTimeout、配置了 IsAutoCloseConnection = true,表面上不应该出问题。

  1. 调试器线程分析 ------ 发现并发访问

在 Visual Studio 中暂停调试,查看线程窗口时发现了关键线索:

线程 ID 当前位置

34996 DiskSubscribeTimer_Triggered(object, SubscribeTaskArgs)

30716 ExecuteSubscribeTask(SubscribeTaskArgs) → RunSubscribeTaskAsync()

38276 DiskSubscribeTimer_Triggered(object, SubscribeTaskArgs)

虽然代码中用 ConcurrentDictionary 防止了同一个磁盘的并发执行,但不同的订阅任务仍然会并行运行,共享同一个 _db 实例。

问题根因及解决

_db是SqlSugarClient实例,所以,

此实例不是线程安全的。 当多个线程同时通过同一个 SqlSugarClient 实例操作数据库时,内部的连接/命令对象会发生竞争,导致 SQLite 底层返回 SQLITE_MISUSE(即 bad parameter or other API misuse)。

整个调用链路如下:

这个错误的迷惑性在于:

  • 连接字符串完全正确
  • IsAutoCloseConnection = true 看似已经处理了连接释放
  • 异常只在多任务并发时偶发出现
  • 异常信息指向"连接错误",容易误导排查方向

将SqlSugarClient(非线程安全)改为SqlSugarScope(线程安全),即可

SqlSugarClient vs SqlSugarScope的区别,列个对比