数据库事务Link to this heading

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

管理数据库事务Link to this heading

Django 默认的事务行为Link to this heading

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

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

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

连结事务与 HTTP 请求Link to this heading

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

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

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

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

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

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

non_atomic_requests(using=None)Link to this definition

该装饰器会为指定视图取消 ATOMIC_REQUESTS 的影响。

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

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

显式控制事务Link to this heading

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

atomic(using=None, savepoint=True)Link to this definition

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

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

atomic 既可用作 decorator:: :

Code
from django.db import transaction

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

也可用作 context manager:: :

Code
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 来允许自然处理完整性错误:

Code
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 禁用了一些API。在 atomic 块中试图提交、回滚或改变数据库连接的自动提交状态将引发异常。

atomic 带有 using 参数,这个参数是数据库名字。如果这个参数没有提供,Django 会使用默认数据库。

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

  • 当进入最外面的 atomic 块时打开事务;

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

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

  • 离开块的最外层时提交或回滚事务。

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

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

自动提交Link to this heading

为什么 Django 使用自动提交Link to this heading

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

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

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

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

停用事务管理Link to this heading

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

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

提交后Link to this heading

有时你需要执行与当前数据库事务相关的操作,但前提是事务成功提交。例子可能包含 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)Link to this definition

将任意函数(无参数)传递给 on_commit():

Code
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:: 包装函数

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

传入的函数将在成功提交调用“on_commit()”的假设数据库写操作后立即被调用。

无任何活动事务时调用 on_commit() ,则回调函数会立即执行。

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

保存点Link to this heading

正确处理保存点(即嵌套了 atomic() 块)。也就是说,注册在保存点后的 on_commit()  的调用(嵌套在 atomic() 块)将在外部事务被提交之后调用,但如果在事务期间回滚到保存点或任何之前的保存点之前,则不会调用:

Code
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

另一方面,当保存点回滚时(因引发异常),内部调用不会被调用:

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

执行顺序Link to this heading

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

异常处理Link to this heading

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

执行时间Link to this heading

你的回调会在成功提交之后执行,因此在回调中的错误不会导致事务回滚。它们会在事务成功之后有条件的执行,但它们不是事务的一部分。对于预期的用例(邮件通知,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() 将导致错误。

在测试中使用Link to this heading

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

为什么没有事务回滚钩子?Link to this heading

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

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

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

底层APILink to this heading

自动提交Link to this heading

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

get_autocommit(using=None)Link to this definition
set_autocommit(autocommit, using=None)Link to this definition

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

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

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

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

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

事务Link to this heading

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

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)Link to this definition
rollback(using=None)Link to this definition

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

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

保存点Link to this heading

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

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

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

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

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

savepoint(using=None)Link to this definition

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

savepoint_commit(sid, using=None)Link to this definition

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

savepoint_rollback(sid, using=None)Link to this definition

回滚事务来保存 sid

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

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

clean_savepoints(using=None)Link to this definition

重置用于生成唯一保存点ID的计数器。

下面的例子演示保存点的用法:

Code
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)Link to this definition
set_rollback(rollback, using=None)Link to this definition

当存在内部原子块时,设置回滚标记为 True 将强制回滚。这对于触发回滚而不引发异常可能很有用。

将它设置为 False 会防止这样的回滚。在这样做之前,确保你已经将事务回滚到当前原子块中一个正常的保存点。否则你会破坏原子性并且可能发生数据损坏。

特定于数据库的注释Link to this heading

SQLite 中的保存点Link to this heading

虽然 SQLite 支持保存点时,但 sqlite3 模块中的一个设计缺陷使得它们几乎无法使用。

当启用自动提交时,保存点没有意义。当关闭时,sqlite3 会在保存点语句之前隐式提交。(事实上,它会在除了 SELECT, INSERT, UPDATE, DELETE and REPLACE 之前的任何语句之前提交)这个 Bug 有两个后果:

  • 保存点的底层API只能在事务中可用,即在 atomic() 块中。

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

MySQL 中的事务Link to this heading

如果你正在使用 MySQL,表可能支持或不支持事务;它取决于 MySQL 版本和表的类型。(表类型是指 "InnoDB" 或 "MyISAM" 之类的东西)MySQL 事务的特性超出了本文的范围,但 MySQL 站点有 MySQL 事务的相关信息。

如果 MySQL 安装时没有支持事务,然后 Django 将始终在自动提交模式中运行:语句将在它们调用的时候被执行和提交。如果 MySQL 安装时支持了事务,Django 将像本文说的那样处理事务。

处理 PostgreSQL 事务中的异常Link to this heading

在一个事务里,当对 PostgreSQL 游标的调用引发了异常(通常是 IntegrityError),在同一事务中的随后的 SQL 将会出现 "当前事务中止,查询被忽略,直到事务块结束" 的错误 。虽然简单实用 save() 不太可能在 PostgreSQL 中引发异常,但是有更高级的用法模式,比如保存具有唯一字段的对象,保存使用 force_insert/force_update 标记,或调用自定义的 SQL。

有几种方法来从这种错误中恢复。

事务回滚Link to this heading

第一个选项是回滚整个事务。比如:

Code
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() 做的改变会丢失,即使操作本身没有引发错误。

保存点回滚Link to this heading

你可以使用 savepoints 来控制回滚的程度。执行可能失败的数据库操作之前,你可以设置或更新保存点;这样,如果操作失败,你可以回滚单一的错误操作,而不是回滚整个事务。比如:

Code
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() 引发异常的情况下被撤销。