数据库事务 — Django 文档

来自菜鸟教程
Django/docs/2.2.x/topics/db/transactions
跳转至:导航、​搜索

数据库事务

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)

这个装饰器将否定效果 :设置:`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 的行为是未指定的并且依赖于数据库。

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

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

例如,给定带有 active 字段的 MyModel,如果将 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:
    ...

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

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

在幕后,Django 的事务管理代码:

  • 进入最外层的 atomic 区块时开启交易;

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

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

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

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

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

性能注意事项

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


自动提交

为什么 Django 使用自动提交

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

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

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

为避免这种情况,您可以关闭事务管理,但不建议这样做。


停用事务管理

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

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


提交后执行操作

有时您需要执行与当前数据库事务相关的操作,但前提是该事务成功提交。 示例可能包括 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() 块)被正确处理。 也就是说,一个 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() 的情况下按顺序执行函数的行为相同。


执行时间

您的回调在 成功提交后执行 ,因此回调中的失败不会导致事务回滚。 它们在交易成功后有条件地执行,但它们不是交易的 部分 。 对于预期的用例(邮件通知、Celery 任务等),这应该没问题。 如果不是(如果您的后续操作非常重要,以至于其失败应该意味着事务本身失败),那么您就不想使用 on_commit() 钩子。 相反,您可能需要 两阶段提交 ,例如 Python DB-API 规范中的 psycopg 两阶段提交协议支持 和 可选的两阶段提交扩展[ X194X]。

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

当处于自动提交模式并且在 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、PostgreSQL、Oracle 和 MySQL(使用 InnoDB 存储引擎时)后端。 其他后端提供保存点功能,但它们是空操作——它们实际上不做任何事情。

如果您使用自动提交(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 支持保存点,但 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() 所做的更改将丢失,即使该操作本身没有引发错误。


保存点回滚

您可以使用 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() 引发异常的情况下不会被撤消。