数据库事务

Django为您提供了一些方法来控制数据库事务的管理方式。

管理数据库事务

Django的默认事务行为

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

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

由于性能的原因,Django的TestCase类也将每个测试包装在一个事务中。

将事务绑定到HTTP请求

处理网上交易的常用方法是在交易中包装每个请求。 在要为其启用此行为的每个数据库的配置中将ATOMIC_REQUESTS设置为True

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

您可以在视图代码中使用保存点执行子事务,通常使用atomic()上下文管理器。 但是,在视图的最后,无论是全部还是全部都不会被提交。

警告

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

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

当视图返回一个StreamingHttpResponse时,读取响应的内容通常会执行代码来生成内容。 由于视图已经返回,所以这些代码在事务之外运行。

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

实际上,这个特性简单地将每个视图函数包装在下面描述的atomic()装饰器中。

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

When ATOMIC_REQUESTS is enabled, it’s still possible to prevent views from running in a transaction.

non_atomic_requests(using=None)[source]

This decorator will negate the effect of ATOMIC_REQUESTS for a given view:

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来控制数据库事务。

atomicusing = Nonesavepoint = True[source]

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

atomic blocks can be nested. 在这种情况下,当内部块成功完成时,如果稍后在外部块中引发异常,则其效果仍可以回滚。

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

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

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的行为是未指定的且与数据库相关。

回滚事务时,您可能需要手动还原模型状态。

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

For example, given MyModel with an active field, this snippet ensures that the if obj.active check at the end uses the correct value if updating active to True fails in the transaction:

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:
    ...

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

atomic takes a using argument which should be the name of a database. 如果没有提供这个参数,Django使用"default"数据库。

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

  • 进入最外面的atomic块时打开一个事务;
  • 当输入一个内部的atomic块时创建一个保存点;
  • 退出内部块时释放或回滚到保存点;
  • 在退出最外面的块时提交或回滚事务。

您可以通过将savepoint参数设置为False来禁止为内部块创建保存点。 如果发生异常,Django将在退出第一个父块时使用保存点执行回滚(如果有),否则执行回滚。 原子依然由外部交易保证。 只有在保存点的开销很明显的情况下才能使用这个选项。 它有破坏上述错误处理的缺点。

当自动提交被关闭时,您可以使用atomic 它将只使用保存点,即使是最外面的块。

性能考虑

打开的事务对于您的数据库服务器而言具有性能成 为了尽量减少这种开销,尽可能缩短交易时间。 如果你在Django请求/响应周期以外的长时间运行的进程中使用atomic(),这一点尤其重要。

自动提交¶ T0>

为什么Django使用自动提交

在SQL标准中,每个SQL查询都会启动一个事务,除非已经有一个事务处于活动状态。 这些交易必须被明确承诺或回滚。

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

PEP 249, the Python Database API Specification v2.0, requires autocommit to be initially turned off. Django覆盖这个默认值并打开自动提交。

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

取消激活事务管理

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

这要求您明确地提交每个事务,甚至是由Django或第三方库启动的事务。 因此,这最适用于您要运行自己的事务控制中间件或执行一些非常奇怪的事情的情况。

在commit 之后执行动作

有时您需要执行与当前数据库事务相关的操作,但仅当事务成功提交时才会执行。 Examples might include a Celery task, an email notification, or a cache invalidation.

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

on_commitfuncusing = None[source]

将任何函数(不带参数)传递给on_commit()

from django.db import transaction

def do_something():
    pass  # send a mail, invalidate a cache, fire off a Celery task, etc.

transaction.on_commit(do_something)

你也可以用lambda包装你的函数:

transaction.on_commit(lambda: some_celery_task.delay('arg1'))

传入的函数将在调用on_commit()时假定的数据库写入后立即调用。

如果在没有活动事务时调用on_commit(),则会立即执行回调。

如果这个假设的数据库写入被回滚(通常是在atomic()块中引发了一个未处理的异常),你的函数将被丢弃,并且永远不会被调用。

保存点¶ T0>

保存点(即嵌套的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()

执行顺序

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

异常处理

如果一个人在提交给定的事务中的函数抛出一个未捕获的异常,在同一交易不迟注册功能将运行。 当然,这与你自己在没有on_commit()的情况下顺序执行函数一样。

执行时间

Your callbacks are executed after a successful commit, so a failure in a callback will not cause the transaction to roll back. They are executed conditionally upon the success of the transaction, but they are not part of the transaction. 对于预期的用例(邮件通知,芹菜任务等),这应该没问题。 如果不是这样(如果后续操作非常重要,以至于失败将意味着事务本身的失败),那么你不想使用on_commit()钩子。 相反,您可能需要两阶段提交,如Python DB-API中的psycopg两阶段提交协议支持可选的两阶段提交扩展说明书 T2>。

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

当处于自动提交模式并在atomic()块之外时,函数将立即运行,而不是提交。

on-commit函数只能用于autocommit modeatomic()(或ATOMIC_REQUESTS)事务API。 当禁用自动提交并且您不在原子块内时调用on_commit()将导致错误。

在测试中使用

Django的TestCase类将每个测试包装在一个事务中,并在每次测试之后回滚该事务,以便提供测试隔离。 这意味着事务没有被实际提交,因此你的on_commit()回调将永远不会运行。 如果您需要测试on_commit()回调的结果,请改为使用TransactionTestCase

为什么没有回滚钩子?

一个回滚钩子比一个提交钩子更难实现,因为各种各样的东西都会导致隐式的回滚。

例如,如果你的数据库连接因为你的进程没有机会正常关闭而被丢弃,你的回滚钩将永远不会运行。

解决方法很简单:不是在原子块(事务)期间执行某些操作,而是在事务失败时撤消它,使用on_commit()来延迟执行,直到事务成功。 解开你从未做过的事情要容易得多!

低级API

警告

如果可能的话,总是比较喜欢atomic() 它说明了每个数据库的特性,并防止无效的操作。

低层次的API只有在你实现自己的事务管理时才有用。

自动提交¶ T0>

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

get_autocommit(using=None)[source]
set_autocommitautocommitusing = None[source]

These functions take a using argument which should be the name of a database. 如果没有提供,Django使用"default"数据库。

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

关闭自动提交后,您将获得数据库适配器的默认行为,而Django不会帮助您。 虽然该行为在 T0> PEP 249 仔细阅读您正在使用的适配器的文档。

您必须确保没有事务处于活动状态,通常在重新打开自动提交之前,通过发出commit()rollback()

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

交易¶ T0>

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

Django不提供API来启动事务。 开始事务的预期方式是使用set_autocommit()禁用自动提交。

一旦你在一个事务中,你可以选择使用commit()应用所做的更改,或者使用rollback() 这些函数在django.db.transaction中定义。

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

These functions take a using argument which should be the name of a database. 如果没有提供,Django使用"default"数据库。

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

保存点¶ T0>

保存点是交易中的一个标记,使您能够回滚部分交易,而不是完整的交易。 SQLite(≥3.6.8),PostgreSQL,Oracle和MySQL(当使用InnoDB存储引擎时)保存点可用于后端。 其他后端提供保存点功能,但是它们是空操作 - 它们实际上没有做任何事情。

如果您使用自动提交,Django的默认行为,保存点并不是特别有用。 但是,一旦用atomic()打开一个事务,就会建立一系列等待提交或回滚的数据库操作。 如果发出回滚,则整个事务将回滚。 Savepoints provide the ability to perform a fine-grained rollback, rather than the full rollback that would be performed by transaction.rollback().

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

每个函数都使用using If no using argument is provided then the "default" database is used.

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

savepoint(using=None)[source]

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

savepoint_commitsidusing = None[source]

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

savepoint_rollbacksidusing = 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_rollbackrollbackusing = None[source]

将回滚标志设置为True强制退出最内层的原子块时进行回滚。 这可能会触发回滚而不会引发异常。

将它设置为False可以防止这种回滚。 在此之前,确保您已经将事务回滚到当前原子块中的已知良好保存点! 否则,你打破原子性和数据损坏可能发生。

特定于数据库的注释

SQLite中的保存点

While SQLite ≥ 3.6.8 supports savepoints, a flaw in the design of the sqlite3 module makes them hardly usable.

当启用自动提交时,保存点没有意义。 When it’s disabled, sqlite3 commits implicitly before savepoint statements. (事实上​​,它在SELECTINSERTUPDATEDELETEREPLACE 这个错误有两个后果:

  • 保存点的低级API只能在事务内部使用, atomic()块内。
  • 自动提交关闭时,使用atomic()是不可能的。

MySQL中的事务

如果你使用的是MySQL,你的表可能支持或不支持事务。这取决于你的MySQL版本和你正在使用的表类型。 (通过“表格类型”,我们的意思是像“InnoDB”或“MyISAM”)。 MySQL transaction peculiarities are outside the scope of this article, but the MySQL site has information on MySQL transactions.

If your MySQL setup does not support transactions, then Django will always function in autocommit mode: statements will be executed and committed as soon as they’re called. 如果您的MySQL设置支持事务,Django将按照本文档中的说明处理事务。

处理PostgreSQL事务中的异常

注意

只有在您实施自己的交易管理时,本节才有意义。 在Django的默认模式下,这个问题不会发生,并且atomic()会自动处理它。

在事务内部,当对PostgreSQL游标的调用引发异常(通常是IntegrityError)时,同一事务中的所有后续SQL都将失败,并显示错误“当前事务中止,查询忽略直到事务结束块”。 While simple 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 flag, or invoking custom 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

In this example, a.save() will not be undone in the case where b.save() raises an exception.