Django 的 on_commit 有什么用例?

sof*_*arn 10 python django transactions commit

阅读本文档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
Run Code Online (Sandbox Code Playgroud)

但为什么不像平常一样编写没有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
Run Code Online (Sandbox Code Playgroud)

它更容易阅读,因为它不需要更多关于 Django API 的知识,并且语句按照执行的顺序排列。测试更容易,因为您不必为 Django 使用任何特殊的测试类。

那么钩子的用例是什么on_commit

Tim*_*all 17

Django 文档中给出的示例代码是transaction.on_commit(lambda: some_celery_task.delay('arg1')),它可能是专门因为这会出现很多 celery 任务。

想象一下,如果您在事务中执行以下操作:

my_object = MyObject.objects.create()
some_celery_task.delay(my_object.pk)
Run Code Online (Sandbox Code Playgroud)

然后在你的芹菜任务中你尝试这样做:

@app.task
def some_celery_task(object_pk)
    my_object = MyObject.objects.get(pk=object_pk)
Run Code Online (Sandbox Code Playgroud)

这可能在很多时候都有效,但随机地你会遇到无法找到对象的错误(取决于工作任务运行的速度,因为它是竞争条件)。这是因为您MyObject在事务中创建了一条记录,但在COMMIT运行 a 之前它实际上在数据库中不可用。Celery 无法访问该打开的事务,因此需要. COMMIT还有一种非常现实的可能性是,稍后的某些事情会导致 aROLLBACK并且 celery 任务实际上不应该被调用。

所以......你需要做:

my_object = MyObject.objects.create()
transaction.on_commit(lambda: some_celery_task.delay(my_object.pk))
Run Code Online (Sandbox Code Playgroud)

现在,直到调用后实际MyObject将其保存到数据库后,才会调用 celery 任务。COMMIT

不过,我应该指出,这主要只是当您不使用时才需要考虑的问题AUTOCOMMIT(这实际上是默认设置)。如果您处于AUTOCOMMIT模式,那么您可以确定提交已作为 a.create()或的一部分完成.save()。但是,如果您的代码库有可能在 a 中被调用,@transaction.atomic()那么它就不再AUTOCOMMIT需要.on_commit(),所以最好/最安全的是始终使用它。


Yev*_*mak 7

Django 文档:

Django 提供 on_commit() 函数来注册事务成功提交后应执行的回调函数

这是主要目的。事务是您希望以原子方式处理的工作单元。它要么完全发生,要么根本不发生。这同样适用于您的代码。如果数据库操作期间出现问题,您可能不需要执行某些操作。

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

  1. 用户将其注册数据发送到我们的端点,我们对其进行验证等。
  2. 我们将新用户保存到数据库中。
  3. 我们通过电子邮件向他发送一封“你好”信,其中包含用于确认他的帐户的链接。

如果第 2 步出现问题,我们不应该执行第 3 步。

我们可以认为,我会得到一个异常,并且也不会执行该代码。为什么我们还需要它?

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

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

  • 验证用户的订单。
  • 检查数据库是否可以完成。
  • 如果可以的话,我们需要向第 3 方 CRM 发送包含订单详细信息的请求。
  • 如果不能,那么我们应该在另一个第三方创建支持票证。
  • 将用户的订单保存到数据库,更新用户的模型。
  • 向负责订单的员工发送消息通知。
  • 保存信息,该员工的通知已成功发送到数据库。

你可以想象,如果我们没有在这种情况下,我们会陷入什么样的混乱,而且我们对此on_commit非常重视。try-catch