Django 的 on_commit 有什么用途?

8

阅读此文档:https://docs.djangoproject.com/en/4.0/topics/db/transactions/#django.db.transaction.on_commit

on_commit 的使用场景。

with transaction.atomic():  # Outer atomic, start a new transaction
    transaction.on_commit(foo)
    # Do things...

    with transaction.atomic():  # Inner atomic block, create a savepoint
        transaction.on_commit(bar)
        # Do more things...

# foo() and then bar() will be called when leaving the outermost block

但是为什么不直接写常规代码而不使用 on_commit 钩子呢?像这样:

with transaction.atomic():  # Outer atomic, start a new transaction
    # Do things...

    with transaction.atomic():  # Inner atomic block, create a savepoint
        # Do more things...

foo()
bar()

# foo() and then bar() will be called when leaving the outermost block

这篇文章更容易阅读,因为不需要太多的Django应用程序编程接口的知识,语句以它们被执行的顺序排列。对于测试也更容易,因为您不必使用任何特殊的Django测试类。

那么on_commit钩子的用例是什么呢?

2个回答

16
在 Django 文档中给出的示例代码是 transaction.on_commit(lambda: some_celery_task.delay('arg1')),很可能是因为在 celery 任务中经常遇到这种情况。
想象一下,如果您在事务中执行以下操作:
my_object = MyObject.objects.create()
some_celery_task.delay(my_object.pk)

然后在你的celery任务中尝试执行以下操作:

@app.task
def some_celery_task(object_pk)
    my_object = MyObject.objects.get(pk=object_pk)

这可能在大多数情况下运行良好,但随机地会出现错误,因为它无法找到对象(这取决于工作任务运行的速度,因为这是竞争条件)。这是因为您在事务中创建了一个MyObject记录,但是直到运行COMMIT时它才实际上可用于数据库。 Celery无法访问该打开的事务,因此需要在COMMIT之后运行。还有非常真实的可能性,即稍后出现ROLLBACK导致永远不应该调用celery任务。

所以...你需要做:

my_object = MyObject.objects.create()
transaction.on_commit(lambda: some_celery_task.delay(my_object.pk))

现在,只有在MyObject被保存到数据库后并调用了COMMIT之后,才会调用celery任务。

需要注意的是,这主要是当您没有使用AUTOCOMMIT(实际上是默认设置)时才需要考虑。如果您处于AUTOCOMMIT模式,则可以确保提交已作为.create().save()的一部分完成。但是,如果您的代码可能在@transaction.atomic()中被调用,那么它就不再是AUTOCOMMIT,需要使用.on_commit(),因此最好/最安全的方法是始终使用它。


6

Django文档:

Django提供了on_commit()函数来注册回调函数,这些函数应该在事务成功提交后执行。

这是主要目的。事务是您希望原子地处理的工作单元。它要么完全发生,要么根本不发生。您的代码也是一样。如果在DB操作期间出现问题,您可能不需要做某些事情。

让我们考虑一些业务逻辑流程:

  1. 用户将其注册数据发送到我们的端点,我们进行验证等操作。
  2. 我们将新用户保存到我们的DB中。
  3. 我们向他发送一封电子邮件,其中包含确认其帐户的链接。

如果在第2步中出现问题,我们不应转到第3步。

我们可以认为,嗯,我会收到异常,并且不会执行那段代码。我们仍然需要它吗?

有时候,您的代码会根据事务在潜在危险的DB操作之前成功的假设而采取行动。例如,您首先想检查是否可以向用户发送电子邮件,因为您知道您的第三方电子邮件经常给您500。在这种情况下,您希望为用户引发500并要求他稍后注册(顺便说一句,这是一个非常糟糕的想法,但这只是一个综合示例)。

当您的函数(例如带有@atomic装饰器)包含大量DB操作时,您肯定不想记住所有变量状态以便在所有与DB相关的代码之后使用它们。像这样:

  • 验证用户的订单。
  • 在DB中检查是否可以完成。
  • 如果可以完成,我们需要向第三方CRM发送订单详细信息的请求。
  • 如果不能,则应在另一个第三方中创建支持票证。
  • 将用户的订单保存到DB中,更新用户的模型。
  • 向负责订单的员工发送信使通知。
  • 将成功发送给员工的通知的信息保存到DB中。

如果在此情况下没有on_commit,并且我们对此进行了真正大的try-catch,那么您可以想象我们会有多乱。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接