Xamarin安卓App中SQLite频繁触发database locked/busy异常排查
看起来你遇到了SQLite连接管理导致的经典锁问题——这类错误90%以上都是因为连接重复创建、未正确释放,或者线程冲突导致的。结合你的代码,我给你梳理几个关键修复点:
1. 修复连接生命周期管理:别手动Close() + using重复操作
你的代码里多处出现using (db)后又手动调用db.Close(),这是冗余且危险的:using语句会自动调用SQLiteConnection.Dispose(),而Dispose()内部已经会关闭连接。手动提前关闭会导致连接状态异常,第二次执行时可能残留未释放的锁。
错误示例:
var db = _dataBaseConnection.GetSqLiteConnection(); using (db) { // ...操作 db.Close(); // 多余!会导致连接状态异常 }
修复后:
// 直接在using里创建连接,自动管理生命周期 using (var db = _dataBaseConnection.GetSqLiteConnection()) { // ...数据库操作 // 不需要手动Close(),using会自动Dispose并关闭连接 }
2. 统一连接实例:避免重复创建多个连接
SQLite对多连接的支持很差(除非启用WAL模式),如果你的GetSqLiteConnection()每次都返回新的连接实例,第二次执行时可能前一个连接还没完全释放,导致数据库锁冲突。
建议改成:
- 全局持有一个单例连接实例(在应用启动时创建,整个应用生命周期复用)
- 或者用依赖注入框架注册
SQLiteConnection为单例服务
3. 内部方法复用同一个连接,不要额外处理
你的UpdateInventory方法接收连接参数是对的,但要确保这个连接是外层using管理的实例,不要在内部做任何关闭/释放操作——让外层的using统一处理。
优化后的UpdateInventory:
private void UpdateInventory(DbInventory inventory, SQLiteConnection db) { _logger.Debug("Enter"); try { // 直接用传入的连接,外层using会负责释放 var result = db.Update(inventory); } catch (SQLiteException ex) { _logger.Error(ex); throw; } }
4. InsertOrUpdateInventoryEntry的连接处理优化
这个方法的disposeFlg逻辑是对的,但要注意:如果是传入的连接,绝对不要关闭/释放它,因为调用方可能还要复用。同时去掉手动的db.Close():
修复后片段:
if (disposeFlg) { // 只需要Dispose(),内部会自动关闭 db.Dispose(); }
5. 启用WAL模式(推荐)
SQLite默认的DELETE journal模式并发支持极差,启用WAL(Write-Ahead Logging)模式可以大幅提升并发性能,减少锁冲突。在应用启动时执行一次即可(设置是持久化的):
using (var db = _dataBaseConnection.GetSqLiteConnection()) { db.Execute("PRAGMA journal_mode=WAL;"); }
6. 确保数据库操作在同一线程(或线程安全调度)
SQLite连接不是线程安全的,不要在多个线程同时使用同一个连接,也不要在不同线程创建多个连接。如果你的应用有后台线程操作数据库,建议用单线程调度器(比如TaskScheduler.FromCurrentSynchronizationContext()或者自定义线程池)来统一处理所有数据库操作。
完整修复后的核心方法示例
比如InsertOrUpdateInventoryWithoutEntries修改后:
public void InsertOrUpdateInventoryWithoutEntries(Inventory inventory) { _logger.Debug("Enter"); try { using (var db = _dataBaseConnection.GetSqLiteConnection()) { var existingDbInventory = db.Find<DbInventory>(dbInventory => dbInventory.Code == inventory.Code); if (existingDbInventory != null) { if (!existingDbInventory.Finished) { existingDbInventory.Description = inventory.Description; existingDbInventory.OpenTime = inventory.OpenTime; existingDbInventory.Finished = inventory.Finished; existingDbInventory.UseInventoryList = inventory.UseInventoryList; existingDbInventory.PostedToServer = inventory.PostedToServer; existingDbInventory.InventoryListIsDownloaded = inventory.InventoryListIsDownloaded; UpdateInventory(existingDbInventory, db); } } else { db.Insert(DbInventory.FromInventory(inventory)); } } } catch (SQLiteException ex) { _logger.Error(ex); throw; } }
按照这些步骤修复后,第二次执行的锁问题应该就能解决了。
内容的提问来源于stack exchange,提问作者Niklas

