为什么保存、提交或重命名时会出现锁等待超时或死锁?

当你在保存、提交或重命名文档时,如果另一个事务对同一行持有锁,你可能会看到 Lock wait timeout exceeded 或 Deadlock found when trying to get lock。这通常发生在并发提交、长时间运行的后台任务,或重命名一个被许多其他文档链接的文档时。

错误信息

你会看到以下之一,通常出现在“服务器错误”对话框中或错误日志里:

pymysql.err.OperationalError: (1205, 'Lock wait timeout exceeded; try restarting transaction')
pymysql.err.OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction')

在 MariaDB 11.6.2 及更高版本中,你在重命名或繁忙保存时也可能看到这个:

pymysql.err.OperationalError: (1020, "Record has changed since last read in table '<table>'")

<p>Frappe 将这三者都视为死锁,因为从用户的角度来看,MariaDB 较新的快照隔离检查(错误 1020)与错误 1213 是同一个问题。</p>
<h2 id="why-this-happens"><strong>为什么会发生</strong></h2>
<p>InnoDB 是 Frappe 使用的数据库引擎,它会在事务更新或插入某一行时立即锁定该行,并一直持有该锁,直到事务提交或回滚。如果第二个事务需要同一行,它就会等待。默认情况下,MariaDB 最多等待 50 秒(<code>innodb_lock_wait_timeout)后放弃并抛出错误 1205。如果两个事务同时互相等待对方的锁,MariaDB 根本不会等待:它会立即检测到循环等待,回滚其中一个事务,并抛出错误 1213(或者自 MariaDB 11.6.2 起,抛出错误 1020)。

重命名是一个特殊情况。重命名文档会更新其自身的行,然后更新所有指向它的 DocType 中的每个 Link 或 Dynamic Link 字段,全部在同一个事务内完成。重命名一个被许多其他记录引用的文档(例如仓库、物料组或工作流操作)会在多个表上持有锁更长时间。在重命名运行期间,任何其他触及这些相同行的事务(例如针对该仓库保存销售发票)都必须等待,并可能超时。

常见触发原因:

  • 多个用户同时提交更新同一链接记录的文档,例如针对同一客户或仓库的销售发票。
  • 一个后台任务(库存重算、计划任务、批量更新)在用户保存相关文档时持有长事务。
  • 一个浏览器标签页或 API 会话保持打开状态,带有未提交的更改,仍然持有锁。
  • 重命名一个被许多其他文档链接的文档。

对于后台任务,Frappe 已经会自动重试。如果 execute_job 遇到错误 1205 或 1213(或 1020,因为 Frappe 将其映射到相同的死锁检查),它会回滚并以递增的延迟重试该任务最多 5 次。从桌面端进行的保存或提交不会自动重试——用户会立即看到错误。

如何修复

  1. 打开 系统控制台(在顶部搜索栏中搜索)。你需要 系统管理员 角色。
  2. 在 数据库进程 下,打开 显示进程列表。表格会自动填充,无需点击执行,并显示每个打开的数据库会话、其状态以及已运行的时间。
  3. 查找 状态 不是空闲的会话,或者 时间 较高的会话。其 信息 列显示正在运行的查询,如果是一个正在等待其他资源的打开事务,则为空。
  4. 要查看死锁的完整详情,包括是哪个表和行导致的,请从数据库控制台(bench --site <site-name> mariadb</site-name>)运行 SHOW ENGINE INNODB STATUS\G,并阅读 最近检测到的死锁 部分。
  5. 如果某个会话卡住并持有锁,请从同一数据库控制台结束它:
KILL <id>;
</id>

使用进程列表中的 Id。在 Frappe Cloud 上,你无法自行运行 KILL;请提交支持工单并附上进程列表输出。
6. 如果阻塞会话属于后台工作进程,请重启工作进程,而不是直接终止数据库会话:bench restart。如果只有调度器卡住,bench scheduler resume --site <site-name></site-name> 可以在不进行完整重启的情况下恢复它。
7. 重试保存、提交或重命名。

如何预防

  • 后台任务在遇到死锁或锁等待超时时会自动重试最多 5 次,因此繁忙时段的一次性错误通常会自动清除。只有在之后仍然失败,或者用户在桌面端立即被阻塞时才需要介入。
  • 在非繁忙时段重命名一个被许多记录链接的文档(仓库、物料组或成本中心)。
  • 在自定义脚本或报表中,分批提交,而不是在一个事务中循环处理数千条记录。
  • 关闭你保持空闲且带有未保存或未提交更改的浏览器标签页或 API 会话。

如果你使用的是不受支持的版本

如果你的 应用版本 显示 Frappe 或 ERPNext v14 或更早版本,请升级。在这些工单中的几个案例里,真正的原因是较新版本已经修复的慢查询或锁定行为,而生命周期结束的版本不在性能调查的范围内。

如果这不能解决问题

如果错误在非繁忙、非并发使用的情况下仍然持续发生,请提交支持工单,并提供确切的错误文本、您当时正在保存、提交或重命名的文档、您的应用版本,以及错误发生时的进程列表输出(或系统控制台截图)。

  • 后台任务
  • 系统控制台
  • 性能分析与监控