在Django中正确结合Celery任务使用transaction.on_commit的方式
嘿,我来帮你搞清楚这两种写法的区别,以及在你的场景下哪种才是正确的选择:
方式1:不使用transaction.atomic
def order_created(order): order.status = 'created' order.save() transaction.on_commit( partial(send_order_email.delay, order_id=order.id) )
方式2:使用transaction.atomic
def order_created(order): with transaction.atomic(): order.status = 'created' order.save() transaction.on_commit( partial(send_order_email.delay, order_id=order.id) )
哪种正确?为什么?
答案是方式2是正确的,原因要结合你设置的ATOMIC_REQUESTS=False来理解:
当ATOMIC_REQUESTS=False时,Django不会给每个请求自动包裹事务。这时候你单独调用order.save(),它会在一个自动提交的事务中执行——也就是说save()执行完成的瞬间,数据库事务就已经提交了。
这时候再调用transaction.on_commit(),因为当前已经没有活跃的事务了,Django会立即执行你传入的Celery任务回调,这完全违背了我们用on_commit()的初衷:我们本来是想确保只有当订单状态的更新成功提交到数据库后,再触发邮件任务,避免任务执行时数据库还没同步最新数据,导致任务查不到正确的订单状态。
而方式2用transaction.atomic()显式创建了一个事务包裹住save()操作,此时save()的修改会暂时存在于这个未提交的事务中。调用transaction.on_commit()后,回调会等到这个atomic代码块结束(也就是事务成功提交)之后才执行,完美实现了“数据库更新确认后再触发异步任务”的需求。
简单总结:如果你的代码不在一个已有的事务中(比如ATOMIC_REQUESTS=False的场景),想要用transaction.on_commit()确保任务在事务提交后执行,就必须显式用transaction.atomic()包裹住你的数据库操作和on_commit()调用。
备注:内容来源于stack exchange,提问作者swaran

