DEBUG.WIKI
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
搬瓦工;先看线路,再选 VPS;CN2 GIA · 对应线路套餐;查看线路 →

SQLite 开始事务就报嵌套事务,检查 10.0.12 连接池竞态

Microsoft.Data.Sqlite 10.0.12 的连接池交付顺序可能让一个原生连接被再次回收。并发 BEGIN 报事务嵌套时,核对实际驱动、连接所有权及 10.0.13 维护修复,不只增加忙等待。

多个任务各自打开 Microsoft.Data.Sqlite 连接,刚调用 BeginTransaction(deferred: false) 就偶发失败:

SQLite Error 1: 'cannot start a transaction within a transaction'.

如果使用 10.0.12、开启连接池,而且失败发生在应用尚未开始事务时,需要把连接池的原生连接所有权竞态列入检查。不要仅凭这句异常,就认定业务代码嵌套调用了 BEGIN。

这项故障曾在 Linux 多任务争用数据库文件时出现。它不表示所有 SQLite 嵌套事务错误都来自连接池;真正重复开启事务、共享一个连接给并发任务等情况仍要分别排除。

10.0.13 调整了原生连接交给新所有者的顺序

Microsoft.Data.Sqlite 的逻辑连接从池里取得原生连接后,需要发布新的外层所有者,再标记为活动状态。10.0.12 的 Activate 顺序相反:先设置 _active = true,后写入外层连接的弱引用。

在这个间隙,泄漏回收逻辑可能把正在交付的连接看作没有所有者,再次放回池里。两个逻辑连接因此可能拿到同一个原生句柄;其中一个开始事务,另一个随后开始事务时,就会遇到“已经在事务中”的状态。

正式 10.0.13 已把发布所有者放在活动标记之前。对于仍锁定 10.0.12 的项目,应在既有依赖管理入口采用包含该修正的兼容维护版本,并确认部署产物真正加载了它。

不要只看项目文件里的一行版本,还应核对 Microsoft.Data.Sqlite 与实际 Microsoft.Data.Sqlite.Core 的解析结果,以及发布目录是否残留旧 DLL。SQLite 原生库版本和托管驱动版本也要分开记录;更新 sqlite3 命令行工具不会改变这段托管连接池代码。

先区分所有权竞态与忙等待

这条异常的出现位置很重要。记录每个任务的连接开启、事务开始和释放时刻,以及实际驱动版本;不需要把业务数据或连接凭据写入日志。确认每个并发任务都拥有自己的逻辑连接,没有复用同一个 SqliteConnection。

不能因为 BEGIN 之前检查到 autocommit 已打开,就排除所有权竞态:两个所有者之间仍可能在检查后插入操作。反过来,也不能把数据库报告 BUSY 后的重试直接认定为根因,必须与池的交付顺序分别判断。

临时隔离连接池影响时,可在获准环境对原有连接配置显式设置:

Pooling=False

这是 ADO.NET 连接字符串选项,应合入原来的配置,保留数据库位置及其他参数。它会改变连接复用和开销,不应作为无需复查的永久性能方案;内存数据库还必须核对连接存活期,否则可能改变数据库本身的存续。

失败的 BEGIN 不适合直接整段重跑

若事务开始已抛异常,先让拥有该连接的代码结束失败路径,再按应用原有恢复方式重新取得连接。不要在状态不明的连接上反复发送 BEGIN,也不要为解锁删除 journal、WAL 或 SHM 文件。

回滚及重试需要按业务边界决定。数据库异常并不自动撤销已发送的消息或外部调用,不能把整段业务无条件再执行一次。

在原来发生问题的并发方式下复查维护版本,同时验证每个事务的写入和回滚结果、连接回收及后续再次借出。有限次数没有复现只能作为该次验证结果,不能写成所有同类错误已被彻底排除。如果新版本仍失败,应继续核对实际嵌套事务或共享连接,不把旧竞态解释套在所有场景上。