数据库事务

Django 提供了几种控制数据库事务管理的方法。

管理数据库事务

Django 的默认事务行为

Django 的默认行为是在自动提交模式下运行。除非事务处于活动状态,否则每个查询都会立即提交到数据库。请参阅以下详细信息

Django 自动使用事务或保存点来保证需要多个查询的 ORM 操作的完整性,尤其是 delete()update() 查询。

Django 的 TestCase 类还将每个测试包装在一个事务中以提高性能。

将事务绑定到 HTTP 请求

在 Web 上处理事务的一种常见方法是将每个请求包装在一个事务中。在每个要启用此行为的数据库的配置中,将 ATOMIC_REQUESTS 设置为 True

它的工作原理如下。在调用视图函数之前,Django 会启动一个事务。如果响应在没有问题的情况下生成,Django 会提交事务。如果视图产生异常,Django 会回滚事务。

您可以在视图代码中使用保存点执行子事务,通常使用 atomic() 上下文管理器。但是,在视图结束时,所有更改都会被提交或全部不会被提交。

警告

虽然这种事务模型的简单性很有吸引力,但当流量增加时,它也会导致效率低下。为每个视图打开一个事务会产生一些开销。对性能的影响取决于应用程序的查询模式以及数据库处理锁定的效率。

每个请求的事务和流式响应

当视图返回 StreamingHttpResponse 时,读取响应的内容通常会执行生成内容的代码。由于视图已经返回,因此此类代码在事务外部运行。

一般来说,在生成流式响应时不建议写入数据库,因为在开始发送响应后没有合理的方法来处理错误。

实际上,此功能将每个视图函数包装在下面描述的 atomic() 装饰器中。

请注意,只有视图的执行包含在事务中。中间件在事务之外运行,模板响应的渲染也是如此。

当启用 ATOMIC_REQUESTS 时,仍然可以防止视图在事务中运行。

non_atomic_requests(using=None)[source]

此装饰器将否定 ATOMIC_REQUESTS 对给定视图的影响

from django.db import transaction


@transaction.non_atomic_requests
def my_view(request):
    do_stuff()


@transaction.non_atomic_requests(using="other")
def my_other_view(request):
    do_stuff_on_the_other_database()

它仅在应用于视图本身时才有效。

显式控制事务

Django 提供了一个单一的 API 来控制数据库事务。

atomic(using=None, savepoint=True, durable=False)[source]

原子性是数据库事务的定义属性。atomic 允许我们创建一个代码块,在该代码块内保证数据库的原子性。如果代码块成功完成,则更改将提交到数据库。如果出现异常,则更改将回滚。

atomic 块可以嵌套。在这种情况下,当内部块成功完成时,如果稍后在外部块中引发异常,其效果仍然可以回滚。

有时需要确保 atomic 块始终是最外层的 atomic 块,确保在退出块且没有错误时提交所有数据库更改。这称为持久性,可以通过设置 durable=True 来实现。如果 atomic 块嵌套在另一个块中,它会引发 RuntimeError

atomic 可用作 装饰器

from django.db import transaction


@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

上下文管理器

from django.db import transaction


def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():
        # This code executes inside a transaction.
        do_more_stuff()

atomic 包装在 try/except 块中允许自然处理完整性错误

from django.db import IntegrityError, transaction


@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()

    add_children()

在此示例中,即使 generate_relationships() 通过破坏完整性约束导致数据库错误,您也可以在 add_children() 中执行查询,并且来自 create_parent() 的更改仍然存在并绑定到同一个事务。请注意,在调用 handle_exception() 时,generate_relationships() 中尝试执行的任何操作都将已安全回滚,因此异常处理程序也可以在必要时对数据库进行操作。

避免在 atomic 内部捕获异常!

退出 atomic 块时,Django 会查看它是正常退出还是出现异常以确定是否提交或回滚。如果您在 atomic 块内部捕获并处理异常,您可能会隐藏 Django 发生问题的事实。这可能导致意外行为。

这主要关系到 DatabaseError 及其子类(如 IntegrityError)。在发生此类错误后,事务将被破坏,Django 会在 atomic 块结束时执行回滚。如果您尝试在回滚发生之前运行数据库查询,Django 将引发 TransactionManagementError。当 ORM 相关的信号处理程序引发异常时,您也可能会遇到此行为。

捕获数据库错误的正确方法是在 atomic 块周围,如上所示。如有必要,为此目的添加一个额外的 atomic 块。此模式还有另一个优点:它明确限定了如果发生异常将回滚哪些操作。

如果您捕获由原始 SQL 查询引发的异常,Django 的行为是不确定的,并且取决于数据库。

您可能需要在回滚事务时手动恢复应用程序状态。

当发生事务回滚时,模型字段的值不会恢复。除非您手动恢复原始字段值,否则这可能导致模型状态不一致。

例如,给定一个带有 active 字段的 MyModel,这段代码片段确保了最终的 if obj.active 检查在事务中将 active 更新为 True 失败时使用了正确的值。

from django.db import DatabaseError, transaction

obj = MyModel(active=False)
obj.active = True
try:
    with transaction.atomic():
        obj.save()
except DatabaseError:
    obj.active = False

if obj.active:
    ...

这同样适用于可能保存应用程序状态的任何其他机制,例如缓存或全局变量。例如,如果代码在保存对象后主动更新缓存中的数据,建议使用 transaction.on_commit(),以便在事务实际提交后延迟缓存更改。

为了保证原子性,atomic 禁用了某些 API。在 atomic 块内尝试提交、回滚或更改数据库连接的自动提交状态将引发异常。

atomic 接受一个 using 参数,该参数应该是数据库的名称。如果未提供此参数,Django 将使用 "default" 数据库。

在底层,Django 的事务管理代码

  • 在进入最外层的 atomic 块时打开事务;

  • 在进入内部 atomic 块时创建保存点;

  • 在退出内部块时释放或回滚到保存点;

  • 在退出最外层块时提交或回滚事务。

可以通过将 savepoint 参数设置为 False 来禁用内部块的保存点创建。如果发生异常,Django 将在退出第一个具有保存点的父块(如果存在)时执行回滚,否则将退出最外层块时执行回滚。原子性仍然由外部事务保证。仅当保存点的开销明显时才应使用此选项。它的缺点是破坏了上面描述的错误处理。

当自动提交关闭时,可以使用 atomic。它将只使用保存点,即使是最外层块也是如此。

性能注意事项

打开的事务会对您的数据库服务器造成性能开销。为了最大程度地减少此开销,请尽可能缩短事务时间。如果您在长时间运行的进程中使用 atomic()(在 Django 的请求/响应周期之外),这一点尤其重要。

自动提交

为什么 Django 使用自动提交

在 SQL 标准中,每个 SQL 查询都会启动一个事务,除非已经有一个事务处于活动状态。然后必须显式提交或回滚此类事务。

这对于应用程序开发人员来说并不总是方便的。为了缓解这个问题,大多数数据库都提供了一种自动提交模式。当自动提交打开且没有事务处于活动状态时,每个 SQL 查询都会包装在它自己的事务中。换句话说,每个这样的查询不仅会启动一个事务,而且还会根据查询是否成功自动提交或回滚事务。

PEP 249,Python 数据库 API 规范 v2.0,要求最初关闭自动提交。Django 覆盖了此默认设置并打开了自动提交。

要避免这种情况,您可以 停用事务管理,但并不推荐。

停用事务管理

您可以通过在其配置中将 AUTOCOMMIT 设置为 False 来完全禁用给定数据库的 Django 事务管理。如果您这样做,Django 不会启用自动提交,也不会执行任何提交操作。您将获得底层数据库库的常规行为。

这要求您显式提交每个事务,即使是 Django 或第三方库启动的事务也是如此。因此,这最好用于您希望运行自己的事务控制中间件或执行一些非常奇怪的操作的情况。

提交后执行操作

有时您需要执行与当前数据库事务相关的操作,但仅当事务成功提交时才执行。示例可能包括后台任务、电子邮件通知或缓存失效。

on_commit() 允许您注册回调,这些回调将在打开的事务成功提交后执行。

on_commit(func, using=None, robust=False)[source]

将函数或任何可调用对象传递给 on_commit()

from django.db import transaction


def send_welcome_email(): ...


transaction.on_commit(send_welcome_email)

回调不会传递任何参数,但您可以使用 functools.partial() 将它们绑定。

from functools import partial

for user in users:
    transaction.on_commit(partial(send_invite_email, user=user))

在打开的事务成功提交后调用回调。如果事务被回滚(通常是在 atomic() 块中引发未处理的异常时),则回调将被丢弃,并且永远不会被调用。

如果您在没有打开的事务时调用 on_commit(),则回调将立即执行。

注册可能失败的回调有时很有用。传递 robust=True 允许即使当前回调引发异常也能执行后续回调。所有从 Python 的 Exception 类派生的错误都会被捕获并记录到 django.db.backends.base 记录器中。

您可以使用 TestCase.captureOnCommitCallbacks() 测试使用 on_commit() 注册的回调。

保存点

保存点(即嵌套的 atomic() 块)会得到正确的处理。也就是说,在保存点(在嵌套的 atomic() 块中)之后注册的 on_commit() 可调用对象将在外部事务提交后被调用,但如果在事务期间发生了到该保存点或任何先前保存点的回滚,则不会被调用。

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

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

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

另一方面,当保存点回滚(由于引发异常)时,内部可调用对象将不会被调用。

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

    try:
        with transaction.atomic():  # Inner atomic block, create a savepoint
            transaction.on_commit(bar)
            raise SomeError()  # Raising an exception - abort the savepoint
    except SomeError:
        pass

# foo() will be called, but not bar()

执行顺序

给定事务的提交后函数按注册顺序执行。

异常处理

如果在给定事务中使用 robust=False 注册的一个提交后函数引发了未捕获的异常,则同一事务中以后注册的函数将不会运行。这与您自己按顺序执行函数(不使用 on_commit())时的行为相同。

执行时间

您的回调在成功提交之后执行,因此回调中的错误不会导致事务回滚。它们根据事务的成功情况有条件地执行,但它们不是事务的一部分。对于预期的用例(邮件通知、后台任务等),这应该没问题。如果不是(如果您的后续操作非常关键,以至于它的失败意味着事务本身的失败),那么您不希望使用 on_commit() 钩子。相反,您可能希望使用 两阶段提交,例如 psycopg 两阶段提交协议支持Python DB-API 规范中的可选两阶段提交扩展

在提交后恢复连接上的自动提交之前,回调不会运行(因为否则回调中执行的任何查询都会打开一个隐式事务,从而阻止连接恢复到自动提交模式)。

在自动提交模式下且在 atomic() 块之外,该函数将立即运行,而不是在提交时运行。

仅当使用自动提交模式atomic()(或ATOMIC_REQUESTS)事务 API 时,提交函数才有效。当自动提交被禁用且您不在原子块中时调用on_commit()将导致错误。

在测试中使用

Django 的TestCase 类将每个测试包装在一个事务中,并在每个测试后回滚该事务,以提供测试隔离。这意味着永远不会真正提交任何事务,因此您的on_commit() 回调永远不会运行。

您可以使用TestCase.captureOnCommitCallbacks()克服此限制。这会将您的on_commit() 回调捕获到一个列表中,允许您对其进行断言,或通过调用它们来模拟事务提交。

克服此限制的另一种方法是使用TransactionTestCase 而不是TestCase。这意味着您的事务将被提交,并且回调将运行。但是TransactionTestCase会在测试之间刷新数据库,这比TestCase的隔离速度慢得多。

为什么没有回滚钩子?

与提交钩子相比,回滚钩子更难以可靠地实现,因为各种情况都可能导致隐式回滚。

例如,如果您的数据库连接由于您的进程被终止而无法正常关闭而断开,则您的回滚钩子将永远不会运行。

但是有一个解决方案:不要在原子块(事务)期间执行某些操作,然后在事务失败时撤消它,而是使用on_commit()将执行延迟到事务成功之后。撤消您从未执行过的操作要容易得多!

底层 API

警告

如果可能,始终优先使用atomic()。它考虑了每个数据库的特性并防止无效操作。

仅当您正在实现自己的事务管理时,底层 API 才有用。

自动提交

Django 在django.db.transaction 模块中提供了一个 API 来管理每个数据库连接的自动提交状态。

get_autocommit(using=None)[source]
set_autocommit(autocommit, using=None)[source]

这些函数接受一个using 参数,该参数应该是数据库的名称。如果未提供,Django 将使用"default" 数据库。

自动提交最初是开启的。如果您将其关闭,则您有责任恢复它。

一旦您关闭自动提交,您将获得数据库适配器的默认行为,而 Django 不会帮助您。虽然该行为在PEP 249中指定,但适配器的实现并不总是彼此一致。仔细查看您正在使用的适配器的文档。

在重新打开自动提交之前,您必须确保没有活动的事务,通常是通过发出commit()rollback()来实现。

atomic() 块处于活动状态时,Django 将拒绝关闭自动提交,因为这会破坏原子性。

事务

事务是一组原子数据库查询。即使您的程序崩溃,数据库也保证所有更改要么全部应用,要么都不应用。

Django 没有提供开始事务的 API。开始事务的预期方法是使用set_autocommit()禁用自动提交。

进入事务后,您可以选择使用commit()应用到目前为止执行的更改,或者使用rollback()取消它们。这些函数在django.db.transaction中定义。

commit(using=None)[source]
rollback(using=None)[source]

这些函数接受一个using 参数,该参数应该是数据库的名称。如果未提供,Django 将使用"default" 数据库。

atomic() 块处于活动状态时,Django 将拒绝提交或回滚,因为这会破坏原子性。

保存点

保存点是事务中的一个标记,它使您能够回滚事务的一部分,而不是整个事务。保存点可用于 SQLite、PostgreSQL、Oracle 和 MySQL(当使用 InnoDB 存储引擎时)后端。其他后端提供了保存点函数,但它们是空操作——它们实际上什么也不做。

如果您使用自动提交(Django 的默认行为),则保存点不是特别有用。但是,一旦您使用atomic()打开事务,您就会构建一系列等待提交或回滚的数据库操作。如果发出回滚,则整个事务将回滚。保存点提供执行细粒度回滚的能力,而不是transaction.rollback()将执行的完整回滚。

atomic() 装饰器嵌套时,它会创建一个保存点以允许部分提交或回滚。强烈建议您使用atomic()而不是下面描述的函数,但它们仍然是公共 API 的一部分,并且没有计划弃用它们。

这些函数中的每一个都接受一个using 参数,该参数应该是行为适用的数据库的名称。如果没有提供using 参数,则使用"default" 数据库。

django.db.transaction 中的三个函数控制保存点

savepoint(using=None)[source]

创建新的保存点。这标志着事务中一个已知处于“良好”状态的点。返回保存点 ID(sid)。

savepoint_commit(sid, using=None)[source]

释放保存点 sid。自创建保存点以来执行的更改将成为事务的一部分。

savepoint_rollback(sid, using=None)[source]

将事务回滚到保存点 sid

如果数据库不支持保存点或处于自动提交模式,则这些函数不会执行任何操作。

此外,还有一个实用程序函数

clean_savepoints(using=None)[source]

重置用于生成唯一保存点 ID 的计数器。

以下示例演示了保存点的用法

from django.db import transaction


# open a transaction
@transaction.atomic
def viewfunc(request):
    a.save()
    # transaction now contains a.save()

    sid = transaction.savepoint()

    b.save()
    # transaction now contains a.save() and b.save()

    if want_to_keep_b:
        transaction.savepoint_commit(sid)
        # open transaction still contains a.save() and b.save()
    else:
        transaction.savepoint_rollback(sid)
        # open transaction now contains only a.save()

保存点可用于通过执行部分回滚来从数据库错误中恢复。如果在 atomic() 块内执行此操作,则整个块仍将回滚,因为它不知道您已在较低级别处理了这种情况!为了防止这种情况,您可以使用以下函数控制回滚行为。

get_rollback(using=None)[source]
set_rollback(rollback, using=None)[source]

将回滚标志设置为 True 会在退出最内层原子块时强制回滚。这可能有助于在不引发异常的情况下触发回滚。

将其设置为 False 会阻止此类回滚。在执行此操作之前,请确保已将事务回滚到当前原子块内的已知良好保存点!否则,您将破坏原子性,并可能导致数据损坏。

数据库特定说明

SQLite 中的保存点

虽然 SQLite 支持保存点,但 sqlite3 模块设计中的一个缺陷使得它们几乎无法使用。

启用自动提交时,保存点没有意义。禁用时,sqlite3 在保存点语句之前隐式提交。 (实际上,它在除 SELECTINSERTUPDATEDELETEREPLACE 之外的任何语句之前都会提交。)此错误有两个后果

  • 保存点的低级 API 只能在事务内使用,即在 atomic() 块内。

  • 在关闭自动提交时无法使用 atomic()

MySQL 中的事务

如果您使用的是 MySQL,则您的表可能支持也可能不支持事务;这取决于您的 MySQL 版本和您使用的表类型。(“表类型”是指“InnoDB”或“MyISAM”之类的东西。)MySQL 事务的特殊性不在本文讨论范围之内,但 MySQL 站点上有 有关 MySQL 事务的信息

如果您的 MySQL 设置不支持事务,则 Django 将始终在自动提交模式下运行:语句将在调用后立即执行并提交。如果您的 MySQL 设置支持事务,则 Django 将按照本文档中所述处理事务。

处理 PostgreSQL 事务中的异常

注意

本节仅在您实现自己的事务管理时才相关。此问题在 Django 的默认模式下不会发生,并且 atomic() 会自动处理它。

在事务中,当对 PostgreSQL 游标的调用引发异常(通常为 IntegrityError)时,同一事务中的所有后续 SQL 都将失败,并显示错误“当前事务已中止,查询在事务块结束前被忽略”。虽然 save() 的基本用法不太可能在 PostgreSQL 中引发异常,但存在可能引发异常的更高级的用法模式,例如保存具有唯一字段的对象、使用 force_insert/force_update 标志保存或调用自定义 SQL。

有几种方法可以从这种错误中恢复。

事务回滚

第一个选项是回滚整个事务。例如

a.save()  # Succeeds, but may be undone by transaction rollback
try:
    b.save()  # Could throw exception
except IntegrityError:
    transaction.rollback()
c.save()  # Succeeds, but a.save() may have been undone

调用 transaction.rollback() 会回滚整个事务。任何未提交的数据库操作都将丢失。在此示例中,a.save() 所做的更改将丢失,即使该操作本身没有引发错误。

保存点回滚

您可以使用 保存点 来控制回滚的范围。在执行可能失败的数据库操作之前,您可以设置或更新保存点;这样,如果操作失败,您可以回滚单个有问题的操作,而不是整个事务。例如

a.save()  # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint()
try:
    b.save()  # Could throw exception
    transaction.savepoint_commit(sid)
except IntegrityError:
    transaction.savepoint_rollback(sid)
c.save()  # Succeeds, and a.save() is never undone

在此示例中,如果 b.save() 引发异常,则 a.save() 不会被撤消。

返回顶部