bigywater.tech
Django · 120

transaction.on_commit() 配合异步任务

把任务放进 transaction.on_commit 是「事务提交之后才触发」的标准做法。这篇把它的执行顺序、失败行为、savepoint 语义以及在测试里的坑逐条验证。

提交后按顺序
on_commit 回调在事务提交之后、按注册顺序执行
robust=True
回调失败只记日志,其余回调继续
TestCase 里不执行
要用 captureOnCommitCallbacks(execute=True)

顺序与时机

实测 · 本机执行输出
  atomic 里注册 A、B: ['块内最后一行', 'A', 'B']
  没有 atomic 时注册 C: ['C', '注册之后的下一行']

回调失败

实测 · 本机执行输出
  atomic 退出时抛出: 通知服务挂了
  已执行的回调: ['first'] (third 没有执行)  订单已提交: True
  robust=True:atomic 正常退出,已执行的回调: ['first', 'third']
  django.db.backends.base 的错误日志: ['Error calling bad in on_commit() during transaction (通知服务挂了).']

回调在提交之后执行,所以它的失败不会让数据回滚。如果任务发不出去就必须保证可靠(不能丢),就需要额外的机制(比如把任务先写进数据库表,再由后台可靠地投递——这是设计上的建议,本文没有实验)。on_commit 本身是「至多一次」:提交成功后、回调执行前进程崩溃,回调就丢了(本文没有模拟崩溃)。

嵌套事务

实测 · 本机执行输出
  执行了: ['outer', 'inner2(成功)']
  外层整体回滚:执行了 []

在测试里

实测 · 本机执行输出
  [captureOnCommitCallbacks]   捕获到 1 个回调,execute=True 后 ran = ['cb']
  [TestCase]                   atomic 块结束后回调是否执行: []
  [TransactionTestCase]        atomic 块结束后回调是否执行: ['cb']

用 manage.py test 真实跑出的结果。测试 on_commit 的逻辑用 captureOnCommitCallbacks,或者 TransactionTestCase(更慢)。

分步演示

on_commit 的规则
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;测试用 manage.py test lab -v 0
    • 没有验证:多数据库(using=)、on_commit 与 Celery 的组合、进程在回调之前崩溃时的丢失、on_commit 里再注册 on_commit 的嵌套行为(仅测了 autocommit 下立即执行)
    京ICP备2026067502号-1京公网安备11010502063487号