博客 / 詳情

返回

.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,表面上不應該出問題。

2. 調試器線程分析 —— 發現併發訪問

在 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)。

整個調用鏈路如下:

image

這個錯誤的迷惑性在於:

  • 連接字符串完全正確
  • IsAutoCloseConnection = true 看似已經處理了連接釋放
  • 異常只在多任務併發時偶發出現
  • 異常信息指向"連接錯誤",容易誤導排查方向

將SqlSugarClient(非線程安全)改為SqlSugarScope(線程安全),即可

SqlSugarClient vs SqlSugarScope的區別,列個對比

image

 

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.