bigywater.tech
Django · 45

transaction.on_commit() 执行时机

on_commit 是「事务提交之后再做」的标准写法(发通知、入队任务)。它到底什么时候执行?回滚了怎么办?多个回调互相影响吗?这篇用事件日志把每种情形排出来,并用另一个数据库连接验证「此刻数据是否已经可见」。

提交之后
回调在外层事务提交后执行,此时其它连接已能看到写入
丢弃
回滚(包括保存点回滚)会丢弃在其中注册的回调
robust=True
默认一个回调抛异常会阻止后面的回调;robust=True 则继续执行并记录异常

什么时候执行

实测 · 本机执行输出
   写入 a(还没提交)
   注册回调 A
   注册回调 B(内层)
   内层 atomic 退出(保存点释放,外层还没提交)
   回调 A 执行(另一个连接此刻能看到该行:True)
   回调 B(在内层 atomic 里注册) 执行(另一个连接此刻能看到该行:True)
   外层 atomic 退出(已提交)

回调是在 COMMIT 之后、atomic 块退出时执行的,所以可以放心地在里面通知别人「数据已经在了」(第 106、119、120 篇会用到)。

回滚与 autocommit

实测 · 本机执行输出
   注册回调 R
   事务因异常回滚
   回调 N(autocommit,没有 atomic) 执行
   autocommit 下注册 N 之后

没有事务时没有「提交」这件事,回调立即执行。

保存点

实测 · 本机执行输出
   内层回滚
   回调 外层 执行
   回调 外层-2 执行
   已提交

回调按保存点记录,保存点回滚时一起丢弃。

回调抛异常

实测 · 本机执行输出
   回调 1 执行
   回调 X 抛出异常
   atomic 退出时向外抛出 RuntimeError
  回调抛异常之后,事务里写的行(kept-1)还在吗: True (事务在回调之前已经提交)
  --- robust=True:
   回调 1 执行
   回调 X 抛出异常
   回调 3 执行
  robust=True:没有异常向外抛,kept-2 在库里: True

事务已经提交,异常不会让数据回滚。用 robust=True 让后面的回调不受影响(异常只记录日志)。

回调里的写入

实测 · 本机执行输出
   回调里写库的 SQL:INSERT INTO "lab_acct" ("name") VALUES (

回调里的数据库写入不在原事务里。

分步演示:on_commit 的时机

on_commit
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。PostgreSQL 16.15(本机临时实例,端口 9421);「可见性」用另一个线程里的独立连接查询
    • 没有验证:测试框架里 on_commit 的行为(TestCase 默认不执行,需要 captureOnCommitCallbacks)、多数据库下的 on_commit(using=)、on_commit 与 ATOMIC_REQUESTS 的组合见第 48 篇
    京ICP备2026067502号-1京公网安备11010502063487号