Flutter中Sqflite批量插入时数据库锁死问题求助
解决Sqflite批量插入时的数据库锁死问题
你遇到的核心问题是并发开启过多独立事务,加上事务内部未使用事务对象执行数据库操作,导致数据库资源竞争锁死。咱们一步步拆解问题并修复:
问题根源分析
异步遍历导致的并发事务:
在fetchShops里,你用shopsApiResponse.data!.forEach((element) async{ await insertShops(element); })——forEach是同步方法,但内部的async回调会被同时调度执行,这意味着会同时开启几十个甚至上百个独立的事务(每个insertShops里都开了一个事务),数据库根本处理不过来,直接锁死。事务内未使用事务对象操作:
警告提示你“Make sure you always use the transaction object for database operations during a transaction”,但你的getShopByShopId、deleteImportedShops都是直接获取新的Database实例,而不是用当前事务的txn对象。这相当于在一个事务的上下文里,又去开启了新的数据库连接操作,直接触发了锁冲突。
修复方案
步骤1:把所有批量操作放到一个大事务+Batch里
不要给每个Shop单独开事务,而是把所有Shop的处理逻辑都放到一个全局事务中,用Batch批量执行,这样能大幅减少数据库锁竞争。
步骤2:修改数据库方法,支持传入Transaction对象
让所有数据库操作方法可以接受Transaction参数,在事务内部时使用这个txn执行操作,而不是重新获取Database实例。
具体代码修改
1. 重构fetchShops方法
Future<List<ShopsModel>> fetchShops() async { List<ShopsModel> shopsList = []; int id = 0; String date = ""; List<SyncDataModel> syncList = await DatabaseHelper.instance.getSyncDataHistory(); for (var element in syncList) { id = element.SyncID!; date = element.ShopSyncDate == null ? "" : element.ShopSyncDate!; } String response = await ApiServices.getMethodApi("${ApiUrls.IMPORT_SHOPS}?date=$date"); if (response.isEmpty || response == null) { return shopsList; } var shopsApiResponse = shopsApiResponseFromJson(response); if (shopsApiResponse.data != null) { // 开启一个全局事务处理所有批量操作 Database db = await DatabaseHelper.instance.database; await db.transaction((txn) async { var batch = txn.batch(); bool isFirstSync = syncList.isEmpty; String nowDate = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()); for (var element in shopsApiResponse.data!) { // 用txn对象执行查询、删除、插入操作 var shopsRow = await DatabaseHelper.instance.getShopByShopId(txn, element.shopID!); if (shopsRow.IsModify == 0 || shopsRow.IsModify == null) { int deleteResult = await DatabaseHelper.instance.deleteImportedShops( txn, element.shopID!, DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.parse(element.updatedOn!)) ); if (deleteResult > 0) { print('Shop has been deleted'); } // 把插入操作加入batch batch.insert( "$shopsTable", ShopsModel( shopID: element.shopID, shopName: element.shopName, shopCode: element.shopCode, contactPerson: element.contactPerson, contactNo: element.contactNo, nTNNO: element.nTNNO, regionID: element.regionID, areaID: element.areaID, salePersonID: element.salePersonID, createdByID: element.createdByID, updatedByID: element.updatedByID, systemNotes: element.systemNotes, remarks: element.remarks, description: element.description, entryDate: DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.parse(element.entryDate!)), branchID: element.branchID, longitiude: element.longitiude, latitiude: element.latitiude, googleAddress: element.googleAddress, createdOn: DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.parse(element.createdOn!)), updatedOn: DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.parse(element.updatedOn!)), tradeChannelID: element.tradeChannelID, route: element.route, vPO: element.vPO, sEO: element.sEO, imageUrl: element.imageUrl, IsModify: 0 ).toJson(), conflictAlgorithm: ConflictAlgorithm.replace ); } } // 执行批量插入 await batch.commit(noResult: true); // noResult提升批量操作性能 // 处理同步日期更新(也放到事务里) if (isFirstSync) { await DatabaseHelper.instance.insertSyncDataHistory(txn, SyncDataModel( ShopSyncDate: nowDate, LastSyncDate: nowDate )); } else { await DatabaseHelper.instance.updateShopSyncDate(txn, nowDate, id); } }); } return shopsList; }
2. 修改DatabaseHelper的方法,支持传入Transaction参数
// 修改getShopByShopId,支持传入txn Future<ShopsModel> getShopByShopId(Transaction? txn, int shopID) async { Database db = txn ?? await instance.database; // 补全你的查询逻辑,用db执行查询并返回ShopsModel // 示例: List<Map<String, dynamic>> maps = await db.query( "$shopsTable", where: "ShopID = ?", whereArgs: [shopID] ); return maps.isNotEmpty ? ShopsModel.fromJson(maps.first) : ShopsModel(); } // 修改deleteImportedShops Future<int> deleteImportedShops(Transaction? txn, int shopID, String updatedDate) async { Database db = txn ?? await instance.database; return await db.delete( "$shopsTable", where: 'ShopID = ? AND UpdatedOn <= ?', whereArgs: [shopID, updatedDate] ); } // 修改insertSyncDataHistory Future<void> insertSyncDataHistory(Transaction txn, SyncDataModel row) async { await txn.insert( "$syncDataTable", row.toJson(), conflictAlgorithm: ConflictAlgorithm.replace ); } // 修改updateShopSyncDate Future<void> updateShopSyncDate(Transaction txn, String? pDate, int id) async { await txn.rawUpdate( "UPDATE SyncDataHistory SET ShopSyncDate = ?, LastSyncDate = ? WHERE SyncID = ?", [pDate, pDate, id] ); }
额外优化建议
- 避免在循环里单独使用
await(原来的forEach+async就是典型错误),改用批量处理或者在事务内统一操作。 - 批量操作时,
batch.commit(noResult: true)可以跳过结果返回,大幅提升性能。 - 确保所有在事务内的数据库操作都使用传入的
txn对象,绝对不要在事务里重新获取Database实例。
内容的提问来源于stack exchange,提问作者Osman Javed
相关产品推荐
相关产品推荐

