bigywater.tech
Django · 106

Signal 与事务提交顺序

post_save 里发通知、发消息、触发异步任务,是最常见的用法,也是最容易出事故的地方:信号是在事务提交之前触发的。这篇在真实的 PostgreSQL 上用第二个连接去「偷看」,把这件事量出来。

False → True
atomic 里另一个连接对新数据的可见性:post_save 时 → on_commit 时
回滚
post_save 里已发出的通知收不回来,on_commit 回调则不会执行
autocommit
没有外层事务时 post_save 触发时数据已提交

提交之前 vs 提交之后

实测 · 本机执行输出
   post_save 触发:另一个连接能看到这一行吗?False
   create() 返回
   on_commit 回调:另一个连接能看到吗?True
   atomic 块退出(已提交)

可见性用「另开一个线程、用它自己的数据库连接去查」来测,这是同一个数据库上的第二个会话。异步任务(比如 Celery worker)就是这样的第二个会话:如果在 post_save 里就把任务发出去,worker 可能在事务提交前就来查这行数据,结果查不到。

回滚之后

实测 · 本机执行输出
事务回滚后,库里这个订单存在吗: False  | 外部通知列表: ['已通知外部系统:订单 2 创建']
on_commit 版本的回调有没有执行: 没有(回滚后不会执行)

post_save 里做的外部调用不会因回滚而撤销。

改用 on_commit

实测 · 本机执行输出
回滚: []
提交: ['提交后通知:订单 4']

transaction.on_commit(func) 把回调挂到当前事务上:提交后执行,回滚则丢弃。

没有外层事务时

实测 · 本机执行输出
   post_save 触发:另一个连接能看到这一行吗?True
   on_commit 回调:另一个连接能看到吗?True

autocommit 模式下 on_commit 的回调会立即执行,所以写 receiver 时不必区分有没有外层事务,统一用 on_commit 即可。

分步演示

信号与事务提交
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。自己起的临时 PostgreSQL 16;第二个连接由另一个线程里的 Django ORM 查询提供
    • 没有验证:嵌套 atomic / savepoint 里 on_commit 的行为、on_commit 回调自己抛异常时的后果、测试用例里(TestCase 包在事务里)on_commit 不执行的问题、MySQL 上的行为
    京ICP备2026067502号-1京公网安备11010502063487号