SQLite内存数据库两阶段提交锁冲突及事务处理疑问
我使用SQLite内存数据库做模拟测试,生产环境则基于SQL Server、IBM Db2、Oracle或Sybase这类数据库。当前需要为操作日志构建两阶段提交流程,要求日志与关联的数据库上下文保存同步,部分场景下数据变更和操作日志表位于同一数据库中。
在SQLite内存库中开启事务后,尝试保存context1再保存context2时,约30秒后抛出异常:
SQLite Error 6: 'database table is locked'.
连接字符串:
var connectionString = $"Data Source=file:{DatabaseAlias};mode=memory;cache=shared"; optionsBuilder.UseSqlite(connectionString);
测试代码:
var context1 = factory.CreateDbContext(); var context2 = factory.CreateDbContext(); context1.WithTransaction(() => { var keysFromContext1 = context1.Keys.ToList(); keysFromContext1.First().Name = "Changed First"; context2.WithTransaction(() => { var employeesFromContext2 = context2.Employees.ToList(); employeesFromContext2.First().Name = "Changed Second"; context1.SaveChanges(); context2.SaveChanges(); }); });
事务处理代码:
public void WithTransaction(Action action) { WithTransaction(action, IsolationLevel.ReadUncommitted); } public void WithTransaction(Action action, IsolationLevel isolationLevel) { if (Database.CurrentTransaction != null) { action(); return; } Database.BeginTransaction(isolationLevel); try { action(); Database.CommitTransaction(); } catch { Database.RollbackTransaction(); throw; } }
两个上下文操作不同实体,但仍出现锁冲突。我不得不将默认隔离级别改为ReadUncommitted(脏读),否则锁冲突会在context2.Employees.ToList()执行时就触发。
疑问:即使操作不同实体,是否必须提交/回滚已启动的事务才能处理后续新启动的事务?
这本质是SQLite的事务机制特性导致的,与你生产环境使用的数据库存在核心差异:
SQLite采用库级锁机制:不同于SQL Server、Oracle这类支持行级/表级锁的数据库,SQLite的锁是针对整个数据库的——只要某个连接开启了写事务(哪怕只是做了修改准备),整个库会被锁定,其他连接的写操作甚至部分读操作都会被阻塞,直到当前事务提交或回滚。哪怕操作不同实体,也无法绕过这个限制。
你的代码逻辑放大了冲突:
- 你在context1的事务中嵌套了context2的事务,两个上下文共享同一SQLite内存库连接(
cache=shared仅共享缓存,不共享事务)。 - context1开启事务后,整个库被锁定,此时context2尝试开启新事务并执行读操作,默认隔离级别下会被阻塞,超时后抛出锁错误。改用ReadUncommitted只是测试环境的临时规避方案,在生产环境这么做会引入脏读风险。
- 你在context1的事务中嵌套了context2的事务,两个上下文共享同一SQLite内存库连接(
针对疑问的直接答复:
在SQLite环境下,是的——必须提交或回滚已启动的事务,才能让其他连接的事务正常执行,哪怕操作的是不同实体。但在你生产使用的SQL Server、Db2等数据库中,由于锁粒度更细,不同实体的事务可以并行执行,无需强制提交前置事务。测试环境优化建议:
- 避免在SQLite中编写跨上下文的嵌套事务逻辑,要么将两个上下文的操作合并到同一个事务中,要么先提交context1的事务再处理context2的操作。
- 如果必须模拟两阶段提交场景,可考虑为测试用的每个上下文分配独立的SQLite内存库(但可能不符合“同一数据库”的测试需求),或者改用支持细粒度锁的测试数据库(如SQL Server LocalDB)。
内容的提问来源于stack exchange,提问作者Udontknow

