把任务发给 Celery 之类的 worker 时,worker 可能比你的事务提交得更快。这篇在真实的 PostgreSQL 上用另一个线程当 worker,诚实地看这个竞态在什么条件下出现、什么条件下没有复现。
atomic 里入队,入队后立刻退出 atomic(提交) 共 200 次: {'found': 200}
atomic 里入队,入队后再做 50 ms 别的事才提交 共 40 次: {'missing': 40}
on_commit 里入队 共 200 次: {'found': 200}这个结果需要小心解读:竞态并没有在最短窗口里复现。我用的临时 PostgreSQL 关掉了 fsync 与同步提交,提交本身很快,而 worker 线程被唤醒并发起查询的时间反而更长。一旦提交前多了 50 ms 的其它工作,失败就是必然的。所以「没复现」不代表安全,只代表窗口够短;生产环境里提交更慢(fsync)、事务更长,更容易出现。
视图里直接入队: {'found': 200}
视图里 on_commit 入队: {'found': 200}ATOMIC_REQUESTS = True 时整个视图在一个事务里,视图返回后才提交,视图里的入队天然早于提交。本次 200 次请求都没有失败,同样是因为窗口太短,没有更多结论。
atomic 里入队 + 50 ms 后才提交,worker 第几次尝试查到(0 = 5 次都没查到): {2: 10, 3: 30}重试能补救,但它是让消费端去容忍生产端的错误。生产端用 transaction.on_commit 入队(第 120 篇)才是不产生这个窗口的做法。