数据库事务

Django提供了一些方法来控制如何管理数据库事务。

管理数据库事务

Django的默认事务行为

Django的默认行为是在自动提交模式下运行。除非事务处于活动状态,否则每个查询都会立即提交到数据库。 See below for details .

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

Django TestCase 出于性能原因,类还将每个测试包装在事务中。

将事务与HTTP请求绑定

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

它是这样工作的。在调用视图函数之前,Django启动一个事务。如果生成的响应没有问题,则Django提交事务。如果视图产生异常,Django将回滚事务。

您可以使用视图代码中的保存点执行子事务,通常使用 atomic() 上下文管理器。但是,在视图的末尾,将提交全部或全部更改。

警告

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

按请求事务和流式响应

当视图返回 StreamingHttpResponse ,读取响应的内容通常会执行代码来生成内容。因为视图已经返回,所以此类代码在事务之外运行。

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

实际上,此功能将 atomic() 下面描述的装饰器。

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

什么时候? ATOMIC_REQUESTS 启用后,仍可以阻止视图在事务中运行。

non_atomic_requests(using=None)[源代码]

这个修饰器将否定 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)[源代码]

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

atomic 块可以嵌套。在这种情况下,当一个内部块成功完成时,如果在以后的某个点上在外部块中引发异常,它的效果仍然可以回滚。

它有时很有用,可以确保 atomic 挡路永远是最外面的 atomic 挡路,确保在没有错误地退出挡路时提交任何数据库更改。这称为持久性,可以通过设置 durable=True 。如果 atomic 挡路嵌套在另一个内部,它引发 RuntimeError

atomic 两者都可用作 decorator ::

from django.db import transaction


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

作为一个 context manager ::

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() 仍然存在并绑定到同一个事务中。请注意,在中尝试的任何操作 generate_relationships()handle_exception() 调用,因此如果需要,异常处理程序也可以对数据库进行操作。

避免在内部捕获异常 atomic 你说什么?

退出时 atomic 块,Django查看它是正常退出还是异常退出,以确定是提交还是回滚。如果在 atomic 布洛克,你可能会隐瞒发生问题的事实。这可能导致意外行为。

这主要是因为 DatabaseError 及其子类,如 IntegrityError . 在出现这种错误之后,事务将中断,Django将在 atomic 块。如果在回滚发生之前尝试运行数据库查询,Django将引发 TransactionManagementError . 当与ORM相关的信号处理程序引发异常时,也可能会遇到此行为。

捕获数据库错误的正确方法是 atomic 如上图所示。如有必要,增加一个额外的 atomic 为此阻止。此模式还有另一个优点:它显式地限定发生异常时将回滚哪些操作。

如果捕获原始SQL查询引发的异常,Django的行为是未指定的,并且依赖于数据库。

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

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

例如,给定 MyModel 用一个 active 字段,此代码段确保 if obj.active 如果更新,请在末尾使用正确的值进行检查 activeTrue 交易失败:

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查询都会启动一个事务。然后必须显式提交或回滚此类事务。

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

PEP 249 python数据库API规范v2.0要求自动提交最初关闭。Django将覆盖此默认值并打开自动提交。

为了避免这种情况,你可以 deactivate the transaction management ,但不建议这样做。

停用交易管理

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

这要求您明确地提交每个事务,甚至是由Django或第三方库启动的事务。因此,最好在您想要运行自己的事务控制中间件或做一些真正奇怪的事情的情况下使用它。

提交后执行操作

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

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

on_commit(func, using=None, robust=False)[源代码]

将函数或任何可调用的函数传递给 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() 块)处理正确。也就是说, on_commit() 在保存点(在嵌套的 atomic() 块)将在提交外部事务后调用,但如果在事务期间回滚到该保存点或任何以前的保存点,则不会调用:

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()

执行令

给定事务的on commit函数按注册顺序执行。

异常处理

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

执行时间

您的回调将被执行 after 成功提交,因此回调失败不会导致事务回滚。它们是在事务成功后有条件地执行的,但不是 part 这笔交易的。对于预期的用例(邮件通知、后台任务等),这应该很好。如果不是(如果您的后续操作非常关键,它的失败应该意味着事务本身的失败),那么您不希望使用 on_commit() 胡克。相反,您可能希望 two-phase commit 例如 psycopg Two-Phase Commit protocol support 以及 optional Two-Phase Commit Extensions in the Python DB-API specification

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

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

在提交函数上,只使用 autocommit mode 以及 atomic() (或) ATOMIC_REQUESTS )事务API。调用 on_commit() 当自动提交被禁用并且您不在原子块内时,将导致错误。

在测试中使用

贾戈 TestCase 类将每个测试包装在一个事务中,并在每次测试后回滚该事务,以便提供测试隔离。这意味着没有实际的事务被提交,因此 on_commit() 回调永远不会运行。

您可以通过使用 TestCase.captureOnCommitCallbacks() . 这捕获了您的 on_commit() 列表中的回调,允许您对它们进行断言,或者通过调用它们来模拟事务提交。

另一种克服限制的方法是使用 TransactionTestCase 而不是 TestCase . 这意味着您的事务已提交,回调将运行。然而 TransactionTestCase 在测试之间刷新数据库,这比 TestCase 的孤立。

为什么没有回滚挂钩?

回滚钩子比提交钩子更难可靠地实现,因为许多事情都可能导致隐式回滚。

例如,如果您的数据库连接被中断,因为您的进程在没有机会正常关闭的情况下被终止,那么您的回滚挂钩将永远不会运行。

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

低级API

警告

总是喜欢 atomic() 如果可能的话。它可以解释每个数据库的特性,并防止无效操作。

只有在实现自己的事务管理时,低级API才有用。

自动提交

Django在 django.db.transaction 用于管理每个数据库连接的自动提交状态的模块。

get_autocommit(using=None)[源代码]
set_autocommit(autocommit, using=None)[源代码]

这些函数需要 using 参数,应为数据库的名称。如果没有提供,Django使用 "default" 数据库。

自动提交最初打开。如果你关掉它,你的责任就是恢复它。

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

您必须确保没有活动的事务,通常通过发出 commit() 或A rollback() ,然后再打开自动提交。

Django将拒绝关闭自动提交 atomic() 块是活动的,因为这样会破坏原子性。

交易

事务是数据库查询的原子集。即使程序崩溃,数据库也会保证应用所有更改,或者不应用任何更改。

Django不提供用于启动事务的API。启动事务的预期方法是禁用自动提交 set_autocommit() .

一旦您在事务中,您可以选择应用直到此时为止所执行的更改 commit() 或者用取消它们 rollback() . 这些功能定义在 django.db.transaction .

commit(using=None)[源代码]
rollback(using=None)[源代码]

这些函数需要 using 参数,应为数据库的名称。如果没有提供,Django使用 "default" 数据库。

Django将拒绝提交或回滚 atomic() 块是活动的,因为这样会破坏原子性。

保存点

保存点是事务中的标记,它使您能够回滚事务的一部分,而不是整个事务。保存点可用于sqlite、postgresql、oracle和mysql(使用innodb存储引擎时)后端。其他后端提供了保存点函数,但它们是空操作——实际上它们不做任何事情。

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

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

每个函数都需要 using 参数,该参数应为应用该行为的数据库的名称。如果没有 using 提供参数,然后 "default" 使用数据库。

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

savepoint(using=None)[源代码]

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

savepoint_commit(sid, using=None)[源代码]

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

savepoint_rollback(sid, using=None)[源代码]

将事务回滚到保存点 sid .

如果不支持保存点或数据库处于自动提交模式,这些函数将不起作用。

此外,还有一个实用功能:

clean_savepoints(using=None)[源代码]

重置用于生成唯一保存点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)[源代码]
set_rollback(rollback, using=None)[源代码]

将回滚标志设置为 True 退出最里面的原子块时强制回滚。这对于在不引发异常的情况下触发回滚可能很有用。

将其设置为 False 防止这种回滚。在执行此操作之前,请确保已将事务回滚到当前原子块中已知良好的保存点!否则,您将破坏原子性,并且可能发生数据损坏。

数据库特定说明

sqlite中的保存点

尽管sqlite支持保存点,但 sqlite3 模块使得它们几乎不可用。

启用自动提交时,保存点没有意义。当它被禁用时, sqlite3 在savepoint语句之前隐式提交。(事实上,它在除 SELECTINSERTUPDATEDELETEREPLACE .)此错误有两个后果:

  • 保存点的低级API仅在事务内部可用,即在 atomic() 阻止。

  • 不可能使用 atomic() 关闭自动提交时。

MySQL中的事务

如果您使用的是MySQL,那么您的表可能支持事务,也可能不支持事务;这取决于您的MySQL版本和您使用的表类型。(在“table types”中,我们的意思是“innodb”或“myisam”)mysql事务特性不在本文的讨论范围之内,但是mysql站点 information on MySQL transactions .

如果你的MySQL安装程序 not 支持事务,然后django将始终以自动提交模式运行:一旦调用语句,它们就会被执行和提交。如果你的mysql设置 does 支持事务,Django将按照本文档中的说明处理事务。

在PostgreSQL事务中处理异常

备注

只有当您正在实现自己的事务管理时,此部分才是相关的。此问题不能在Django的默认模式下发生,并且 atomic() 自动处理。

在事务内,当调用PostgreSQL游标时引发异常(通常 IntegrityError), all subsequent SQL in the same transaction will fail with the error "current transaction is aborted, queries ignored until end of transaction block". While the basic use of save() is unlikely to raise an exception in PostgreSQL, there are more advanced usage patterns which might, such as saving objects with unique fields, saving using the 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() 将丢失,即使该操作本身没有引发错误。

保存点回滚

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

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

在这个例子中, a.save() 在以下情况下不会撤销 b.save() 引发异常。