数据库事务P

Django 提供多种方式控制数据库事务。

管理数据库事务P

Django 默认的事务行为P

Django 默认的事务行为是自动提交。除非事务正在执行,每个查询将会马上自动提交到数据库, 详见下文

Django 自动使用事务或还原点,以确保需多次查询的 ORM 操作的一致性,特别是 delete()update() 操作。

由于性能原因,Django 的 TestCase 类同样将每个测试用事务封装起来。

连结事务与 HTTP 请求P

在 Web 里,处理事务比较常用的方式是将每个请求封装在一个事务中。 在你想启用该行为的数据库中,把配置中的参数 ATOMIC_REQUESTS 设置为 True

它是这样工作的:在调用试图方法前,Django 先生成一个事务。如果响应能正常生成,Django 会提交该事务。而如果视图出现异常,Django 则会回滚该事务。

你可以在你的视图代码中使用还原点执行子事务,一般会使用 atomic() 上下文管理器。但是,在视图结束时,要么所有的更改都被提交,要么所有的更改都不被提交。

警告

虽然这种简洁的事务模型很吸引人,但在流量增加时,也会降低效率。为每个视图打开一个事务都会带来一些开销。对性能的影响程度取决于应用执行的查询语句和数据库处理锁的能力。

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

当视图返回一个 StreamingHttpResponse 时,获取该响应的内容总会执行代码,生成内容。由于早就返回了该视图,某些代码会在事务外执行。

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

实际上,此功能只是简单地用下文介绍的 atomic() 装饰器装饰了每个视图函数。

注意,只有视图被限制在事务中执行。中间件在事务之外运行,同理,渲染模板响应也是在事务之外运行的。

即便启用了 ATOMIC_REQUESTS,仍能避免视图在事务中运行。

non_atomic_requests(using=None)[源代码]P

该装饰器会为指定视图取消 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()

只有在它被应用到视图时才会生效。

显式控制事务P

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

atomic(using=None, savepoint=True)[源代码]P

原子性是数据库事务的定义属性。 atomic 允许创建代码块来保证数据库的原子性。如果代码块成功创建,这个变动会提交到数据库。如果有异常,变动会回滚。

atomic 块可以嵌套。在这个例子里,当内部块成功完成时,如果在稍后外部块里引发了异常,则仍可回滚到最初效果。

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() 的改变也会在这里。注意,任何试图在 generate_relationships() 中执行的操作在 handle_exception() 被调用的时候也会安全的回滚,因此异常处理也会在必要的时候在数据库上操作。

要避免在 atomic 内部捕捉异常!

当存在 atomic 块时, Django 查看它是否正常退出或存在异常来决定是提交还是正常回滚。如果你在 atomic 内部捕捉并且处理异常,你可以对 Django 隐藏问题代码。这会导致一些意外的行为。

这主要是 DatabaseError 和它的子类的一个问题(比如 IntegrityError )。出现这样的错误之后,事务会奔溃,并且 Django 将在 atomic 块的末尾执行回滚。如果你打算在回滚发生的时候运行数据库查询,Django 将引发 TransactionManagementError 错误。当 ORM 相关的信号处理程序引发异常时,你也可能遇到这个问题。

捕捉数据库错误的正确的方法是像上方所示那样围绕 atomic 块。如有需要,为此目的可以添加额外的 atomic 块。这个模式有别的优势:如果异常发生,它会明确界定哪些操作将回滚。

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

当回滚事务时,你可能需要手工恢复模型状态。

当事务回滚时,模型字段的值不会被恢复。除非你手工恢复初始的字段值,否则这会导致模型状态不一致。

例如,给定带有 active 字段的 MyModel 模型,如果在事务中更新 activeTrue 失败,那么这个片段确保最后的 if obj.active 检查使用正确的值:

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 会使用默认数据库。

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

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

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

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

性能考虑因素

打开事务会对数据库服务器有性能成本。尽量减少这种开销,要保持事务尽可能简短。如果正在 Django 的请求 / 响应周期之外,在长时间运行的进程中使用 atomic() ,这点尤其重要。

自动提交P

为什么 Django 使用自动提交P

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

这对开发者来说一直很头疼。为了减轻这个问题,大部分数据库提供了自动提交模式。当打开了自动提交,并且没有事务活动时,每一个 SQL 查询将被包含在自己的事务中。换句话说,每一个这种查询不仅会启动一个事务,而且事务也会被自动提交或回滚,这取决于查询是否成功。

PEP 249 (Python 数据库接口规范 v2.0)要求自动提交在初始时是关闭的。Django 会覆盖这个默认值并开启自动提交。

为了避免这种情况,你可以参考 deactivate the transaction management<deactivate-transaction-management> ,但并不推荐这样做。

停用事务管理P

你可以通过设置 AUTOCOMMIT 1False 来对数据库完全禁用 Django 事务管理。如果你这么做了,Django 将不会启动自动提交,而且不会执行任何提交。你将获得底层数据库的常规行为。

这要求你显式地提交每一个事务,即使它们通过 Django 或第三方库启动。因此,这适用于当你想运行事务控制中间件或做一些非常奇怪的事情的情形。

提交后P

有时你需要执行与当前数据库事务相关的操作,但前提是事务成功提交。例子可能包含 Celery  任务,邮件提醒或缓存失效。

Django provides the on_commit() function to register callback functions that should be executed after a transaction is successfully committed:

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

将任意函数(无参数)传递给 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() 块里引发了一个未处理异常),函数将被丢弃且永远不会被调用。

保存点P

正确处理保存点(即嵌套了 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()

执行顺序P

事务提交后的的回调函数执行顺序与当初注册时的顺序一致。

异常处理P

如果一个带有给定事务的 on-commit 函数引发了未捕捉的异常,则同一个事务里的后续注册函数不会被运行。当然,这与你在没有 on_commit() 的情况下顺序执行函数的行为是一样的。

执行时间P

你的回调会在成功提交之后执行,因此在回调中的错误不会导致事务回滚。它们会在事务成功之后有条件的执行,但它们不是事务的一部分。对于预期的用例(邮件通知,Celery任务等等),这些都没问题。如果不是(如果后续行为很严重,它的失败意味着事务本身失败),那么你不能使用 on_commit() 钩子。相反,你可能需要两阶段提交(two-phase commit),比如psycopg两阶段提交协议支持( psycopg Two-Phase Commit protocol support ) 和 Python DB-API 中可选的两阶段提交扩展( optional Two-Phase Commit Extensions in the Python DB-API specification )。

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

当在自动提交模式并且在 atomic() 块外时,函数会立即自动运行,而不会提交。

on-commit 函数仅适用于自动提交模式( autocommit mode ),并且 atomic() (或 ATOMIC_REQUESTS )事务API。当禁用自动提交并且当前不在原子块中时,调用 on_commit() 将导致错误。

在测试中使用P

Django 的 TestCase 类在事务中装饰每个测试,并且在每个测试后回滚事务,为了提供测试隔离。这意味着实际上没有提交任何事务,因此 on_commit() 回调不会运行。如果你需要测试 on_commit() 回调的结果,可以改用 TransactionTestCase

为什么没有事务回滚钩子?P

事务回滚钩子相比事务提交钩子更难实现,因为各种各样的情况都可能造成隐式回滚。

比如,如果数据库连接被删除,因为进程被杀而没有机会正常关闭,回滚钩子将不会运行。

解决方法很简单,与其在执行事务时(原子操作)进行某项操作,当事务执行失败后再取消这项操作,不如使用 on_commit() 来延迟该项操作,直到事务成功后再进行操作。毕竟事务成功后你才能确保之后的操作是有意义的。

底层APIP

警告

应该尽可能使用 atomic() 。它说明了每个数据库的特性,并防止了无效操作。

底层API只在实现事务管理时有用。

自动提交P

Django provides a straightforward API in the django.db.transaction module to manage the autocommit state of each database connection.

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

这些函数使接受一个 using 参数表示所要操作的数据库。如果未提供,则 Django 使用 "default" 数据库。

自动提交默认为开启,如果你将它关闭,自己承担后果。

一旦你关闭了自动提交, Django 将无法帮助你,数据库将会按照你使用的数据库适配器的默认行为进行操作。虽然适配器的标准经过了 PEP 249 详细规定,但不同适配器的实现方式并不总是一致的。你需要谨慎地查看你所使用的适配器的文档。

在关闭自动提交之前,你必须确保当前没有活动的事务,通常你可以执行 commit() 或者 rollback() 函数以达到该条件。

当一个原子 atomic() 事务处于活动状态时, Django 将会拒绝关闭自动提交的请求,因为这样会破坏原子性。

事务P

事务是指具有原子性的一系列数据库操作。即使你的程序崩溃,数据库也会确保这些操作要么全部完成要么全部都未执行。

Django doesn't provide an API to start a transaction. The expected way to start a transaction is to disable autocommit with set_autocommit().

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

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

这些函数使接受一个 using 参数表示所要操作的数据库。如果未提供,则 Django 使用 "default" 数据库。

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

保存点P

保存点在事务中是标记物,它可以使得回滚部分乌市,而不是所有事务。 SQLite, PostgreSQL, Oracle, 和 MySQL (当使用 InnoDB 存储引擎) 后端提供了保存点。其他后端提供了保存点函数,但它们是空操作——它们实际上没有做任何事情。

如果你正在使用 Django 的默认行为——自动提交,保存点并不特别有用。尽管,一旦你用 atomic() 打开了一个事务,那么需要构建一系列的等待提交或回滚的数据库操作。如果发出回滚,那么会回滚整个事务。保存点有能力执行颗粒度级别的回滚,而不是由 transaction.rollback() 执行的完全回滚。

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

这里的每一个函数使用 using 参数,这个参数为应用的数据库名。如果没有 using 参数,那么会使用 "default" 数据库。

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

savepoint(using=None)[源代码]P

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

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

释放保存点 sid 。自保存点被创建依赖执行的更改成为事务的一部分。

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

回滚事务来保存 sid

如果不支持保存点或数据库在自动模式时,这些函数不执行操作。

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

clean_savepoints(using=None)[源代码]P

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

Setting the rollback flag to True forces a rollback when exiting the innermost atomic block. This may be useful to trigger a rollback without raising an exception.

Setting it to False prevents such a rollback. Before doing that, make sure you've rolled back the transaction to a known-good savepoint within the current atomic block! Otherwise you're breaking atomicity and data corruption may occur.

Database-specific notesP

Savepoints in SQLiteP

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

When autocommit is enabled, savepoints don't make sense. When it's disabled, sqlite3 commits implicitly before savepoint statements. (In fact, it commits before any statement other than SELECT, INSERT, UPDATE, DELETE and REPLACE.) This bug has two consequences:

  • The low level APIs for savepoints are only usable inside a transaction ie. inside an atomic() block.
  • It's impossible to use atomic() when autocommit is turned off.

MySQL 中的事务P

If you're using MySQL, your tables may or may not support transactions; it depends on your MySQL version and the table types you're using. (By "table types," we mean something like "InnoDB" or "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. If your MySQL setup does support transactions, Django will handle transactions as explained in this document.

Handling exceptions within PostgreSQL transactionsP

注解

This section is relevant only if you're implementing your own transaction management. This problem cannot occur in Django's default mode and atomic() handles it automatically.

Inside a transaction, when a call to a PostgreSQL cursor raises an exception (typically 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 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.

There are several ways to recover from this sort of error.

Transaction rollbackP

The first option is to roll back the entire transaction. For example:

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

Calling transaction.rollback() rolls back the entire transaction. Any uncommitted database operations will be lost. In this example, the changes made by a.save() would be lost, even though that operation raised no error itself.

Savepoint rollbackP

You can use savepoints to control the extent of a rollback. Before performing a database operation that could fail, you can set or update the savepoint; that way, if the operation fails, you can roll back the single offending operation, rather than the entire transaction. For example:

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.