Skip to main content

数据库事务

Django为您提供了几种方法来控制如何管理数据库事务。

管理数据库事务

Django的默认事务行为

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

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

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

将事务绑定到HTTP请求

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

它工作这样。在调用视图函数之前,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)[源代码]

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

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

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

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

为了保证原子性,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)需要最初关闭autocommit。 Django会覆盖此默认值并打开自动提交。

为了避免这种情况,你可以 停用事务管理,但不推荐。

停用事务管理

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

这需要您明确地提交每个事务,即使是由Django或第三方库启动的事务。因此,这最好用于您想要运行自己的事务控制中间件或做一些奇怪的事情。

在提交后执行操作

New in Django 1.9.

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

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

on_commit(func, using=None)

将任何函数(不带参数)传递给 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() 块中引发未处理的异常时),您的函数将被丢弃,从不调用。

保存点

保存点(即嵌套的 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函数引发了一个未捕获的异常,则该同一个事务中的后续注册函数将不会运行。这当然是相同的行为,如果你自己没有 on_commit() 顺序执行的函数。

执行时间

您的回调被 after 执行成功提交,因此回调中的失败不会导致事务回滚。它们在事务成功时有条件地执行,但它们不是事务的 part。对于预期的用例(邮件通知,芹菜任务等),这应该很好。如果它不是(如果你的后续行动是如此关键,它的失败应该意味着事务本身的失败),那么你不想使用 on_commit() 钩子。相反,你可能想要 two-phase commit,如 psycopg Two-Phase Commit protocol supportoptional Two-Phase Commit Extensions in the Python DB-API specification

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

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

提交功能仅与 自动提交模式atomic() (或 ATOMIC_REQUESTS)事务API一起使用。当禁用自动提交并且您不在原子块内时调用 on_commit() 将导致错误。

在测试中使用

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

为什么没有回滚钩?

回滚钩子比提交钩子更难实现,因为各种各样的事情可能导致隐式回滚。

例如,如果您的数据库连接被删除,因为您的进程被杀死,没有机会正常关闭,您的回滚钩将永远不会运行。

解决方案很简单:不是在原子块(事务)期间执行某些操作,而是在事务失败时撤消它,而是使用 on_commit() 首先延迟执行它,直到事务成功。这是一个很容易撤消的东西,你从来没有在第一个地方!

低级API

警告

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

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

自动提交

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

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

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

自动提交已初始启用。如果你关闭它,你有责任恢复它。

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

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

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

交易

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

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

进入交易后,您可以选择应用您在 commit() 之前执行的更改,或者使用 rollback() 取消更改。这些函数在 django.db.transaction 中定义。

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

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

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

保存点

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

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

atomic() 装饰器嵌套时,它创建一个允许部分提交或回滚的保存点。强烈建议您使用 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≥3.6.8支持保存点,但 sqlite3 模块设计中的缺陷使得它们几乎不可用。

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

  • 保存点的低级API仅在事务内可用。在 atomic() 块内。

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

MySQL中的事务

如果您使用MySQL,您的表可能支持或不支持事务;它取决于您的MySQL版本和您使用的表类型。 (“表类型”,我们的意思是“InnoDB”或“MyISAM”。)MySQL事务特性不在本文的讨论范围之内,但MySQL站点有 information on MySQL transactions

如果你的MySQL设置确实 not 支持事务,那么Django总是在autocommit模式下工作:语句一旦被调用就会被执行和提交。如果你的MySQL设置 does 支持事务,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() 不会被撤消。