You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 16:35:13