首页
/ Drift数据库事务中外部创建语句导致死锁问题分析

Drift数据库事务中外部创建语句导致死锁问题分析

2025-06-28 01:53:40作者:卓艾滢Kingsley

问题现象

在使用Drift数据库库时,开发者遇到一个特殊的事务执行问题:当在事务外部创建SQL语句对象,然后在事务内部执行时,事务会卡死无法完成。具体表现为事务只执行了部分SQL语句后就停滞不前。

问题复现代码

Future<void> changeNoteParent({
  required String noteId,
  required String parentNoteId,
  required String newParentNoteId,
  required int newPositon
}) async {
  final branch = await getBranch(noteId, parentNoteId);
  // 问题点:在事务外部创建更新语句
  final sql = db.update(db.branches)
    ..where((branches) => 
        branches.noteId.equals(noteId) &
        branches.parentNoteId.equals(parentNoteId));
  
  final data = BranchesCompanion(
    parentNoteId: Value(newParentNoteId),
    position: Value(newPositon),
    utcModified: Value(TimeTool.utcms),
  );
  
  await db.transaction(() async {
    await db.customStatement(
      'UPDATE branches SET position = position + 1 WHERE parentNoteId = ? AND position >= ?',
      [newParentNoteId, newPositon],
    );
    // 在事务内部执行外部创建的语句
    await sql.write(data);
    await db.customStatement(
      'UPDATE branches SET position = position - 1 WHERE parentNoteId = ? AND position >= ?',
      [parentNoteId, branch.position],
    );
  });
}

问题本质

这个问题源于Drift库的事务处理机制存在一个缺陷:当SQL语句对象在事务外部创建,但在事务内部执行时,Drift未能正确地将该语句纳入当前事务上下文中。这会导致数据库连接状态不一致,最终引发死锁。

解决方案

临时解决方案

开发者可以简单地将语句创建移到事务内部:

await db.transaction(() async {
  final sql = db.update(db.branches)
    ..where((branches) => 
        branches.noteId.equals(noteId) &
        branches.parentNoteId.equals(parentNoteId));
  
  // 其他操作
  await sql.write(data);
});

根本修复

Drift库的维护者已在最新版本中修复了这个问题(提交7f7d2ab1b0a03383eb6acd30ca376026720ce347)。修复后,无论语句是在事务内部还是外部创建,只要在事务中执行,都会被正确纳入事务上下文。

最佳实践建议

  1. 语句创建位置:为了代码清晰和避免潜在问题,建议总是在事务内部创建需要在该事务中执行的语句对象。

  2. 事务边界明确:确保事务中的所有数据库操作都在事务回调函数内部完成,不要将事务操作分散到多个函数中。

  3. 错误处理:事务中应包含适当的错误处理,确保在发生异常时能够回滚事务。

  4. 版本升级:建议升级到已修复此问题的Drift版本,以获得更稳定的行为。

总结

这个问题展示了数据库事务处理中的一个重要原则:事务内的所有操作应该在同一个上下文中创建和执行。Drift库的修复确保了API行为更加符合开发者的直觉,同时也提醒我们在使用ORM库时要注意事务边界和语句生命周期管理。

登录后查看全文
热门项目推荐
相关项目推荐