数据库事务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的影响。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, durable=False)Link to this definition
原子性是数据库事务的定义属性。
atomic允许创建代码块来保证数据库的原子性。如果代码块成功创建,这个变动会提交到数据库。如果有异常,变动会回滚。atomic块可以嵌套。在这个例子里,当内部块成功完成时,如果在稍后外部块里引发了异常,则仍可回滚到最初效果。有时候,确保一个
atomic块始终是最外层的atomic块是有用的,以确保在退出块时没有错误时提交任何数据库更改。这被称为耐久性,可以通过设置durable=True来实现。如果atomic块嵌套在另一个块内,它会引发一个RuntimeError。atomicis usable both as a decorator:from django.db import transaction @transaction.atomic def viewfunc(request): # This code executes inside a transaction. do_stuff()and as a 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禁用了一些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 会覆盖这个默认值并开启自动提交。
为了避免这种情况,你可以参考 取消事务管理 ,但并不推荐这样做。
停用事务管理Link to this heading
你可以通过设置 AUTOCOMMIT 为 False 来对数据库完全禁用 Django 事务管理。如果你这么做了,Django 将不会启动自动提交,而且不会执行任何提交。你将获得底层数据库的常规行为。
这要求你显式地提交每一个事务,即使它们通过 Django 或第三方库启动。因此,这适用于当你想运行事务控制中间件或做一些非常奇怪的事情的情形。
提交后Link to this heading
有时候,您需要执行与当前数据库事务相关的操作,但只有在事务成功提交时才执行。示例可能包括后台任务、电子邮件通知或缓存失效。
on_commit() 允许您注册在成功提交打开的事务后执行的回调函数:
- on_commit(func, using=None, robust=False)Link to this definition
将一个函数或任何可调用对象传递给 on_commit():
from django.db import transaction
def send_welcome_email(): ...
transaction.on_commit(send_welcome_email)
回调函数不会传递任何参数,但您可以使用 functools.partial() 绑定它们:
from functools import partial
for user in users:
transaction.on_commit(partial(send_invite_email, user=user))
回调函数在成功提交打开的事务后被调用。如果事务被回滚(通常是在 atomic() 块中引发未处理的异常时),回调函数将被丢弃,并且不会被调用。
如果在没有打开事务的情况下调用 on_commit(),回调函数将立即执行。
有时候,注册可能失败的回调是有用的。传递 robust=True 允许在当前回调抛出异常时执行下一个回调。所有派生自 Python 的 Exception 类的错误都会被捕获并记录到 django.db.backends.base 记录器中。
您可以使用 TestCase.captureOnCommitCallbacks() 来测试使用 on_commit() 注册的回调函数。
保存点Link to this heading
正确处理保存点(即嵌套了 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()
执行顺序Link to this heading
事务提交后的的回调函数执行顺序与当初注册时的顺序一致。
异常处理Link to this heading
如果在给定事务中使用 robust=False 注册的一个 on-commit 函数引发了未捕获的异常,那么在同一事务中注册的后续函数都不会运行。这与如果您自己没有使用 on_commit() 顺序执行函数的行为相同。
执行时间Link to this heading
您的回调函数在成功提交之后执行,因此回调中的失败不会导致事务回滚。它们在事务成功的条件下执行,但它们不是事务的一部分。对于预期的用例(邮件通知、后台任务等),这应该是可以接受的。如果不是(如果您的后续操作非常关键,以至于其失败应该意味着事务本身的失败),那么您不应该使用 on_commit() 钩子。相反,您可以考虑使用 two-phase commit,比如 psycopg Two-Phase Commit 协议支持 和 Python DB-API 规范中的可选 Two-Phase Commit 扩展。
直到在提交后的连接上恢复自动提交,调用才会运行。(因为否则在回调中完成的任何查询都会打开一个隐式事务,防止连接返回自动提交模式)
当在自动提交模式并且在 atomic() 块外时,函数会立即自动运行,而不会提交。
on-commit 函数仅适用于自动提交模式( autocommit mode ),并且 atomic() (或 ATOMIC_REQUESTS )事务API。当禁用自动提交并且当前不在原子块中时,调用 on_commit() 将导致错误。
在测试中使用Link to this heading
Django 的 TestCase 类将每个测试包装在一个事务中,并在每个测试后回滚该事务,以提供测试隔离。这意味着实际上从不会提交任何事务,因此您的 on_commit() 回调将永远不会运行。
您可以通过使用 TestCase.captureOnCommitCallbacks() 来克服这个限制。这将您的 on_commit() 回调捕获在一个列表中,允许您对它们进行断言,或者通过调用它们来模拟事务提交。
克服这个限制的另一种方法是使用 TransactionTestCase 而不是 TestCase。这意味着您的事务会被提交,并且回调函数会运行。但是,请注意,TransactionTestCase 在测试之间会刷新数据库,这比 TestCase 的隔离要慢得多。
为什么没有事务回滚钩子?Link to this heading
事务回滚钩子相比事务提交钩子更难实现,因为各种各样的情况都可能造成隐式回滚。
比如,如果数据库连接被删除,因为进程被杀而没有机会正常关闭,回滚钩子将不会运行。
解决方法是:与其在执行事务时(原子操作)进行某项操作,当事务执行失败后再取消这项操作,不如使用 on_commit() 来延迟该项操作,直到事务成功后再进行操作。毕竟事务成功后你才能确保之后的操作是有意义的。
底层APILink to this heading
自动提交Link to this heading
Django provides an 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 的一部分,而且没计划要弃用它们。
Each of these functions takes a using argument which should be the name of
a database for which the behavior applies. If no using argument is
provided then the "default" database is used.
保存点由 django.db.transaction: 中的三个函数来控制:
- savepoint(using=None)Link to this definition
-
创建新的保存点。这标志着事务中已知处于“良好”状态的一个点。返回保存点ID (
sid) 。
- savepoint_create(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的计数器。
下面的例子演示保存点的用法:
from django.db import transaction
# open a transaction
@transaction.atomic
def viewfunc(request):
a.save()
# transaction now contains a.save()
sid = transaction.savepoint_create()
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 有两个后果:
MySQL 中的事务Link to this heading
如果你正在使用 MySQL,表可能支持或不支持事务;它取决于 MySQL 版本和表的类型。(表类型是指 "InnoDB" 或 "MyISAM" 之类的东西)MySQL 事务的特性超出了本文的范围,但 MySQL 站点有 MySQL 事务的相关信息。
如果 MySQL 安装时没有支持事务,然后 Django 将始终在自动提交模式中运行:语句将在它们调用的时候被执行和提交。如果 MySQL 安装时支持了事务,Django 将像本文说的那样处理事务。
处理 PostgreSQL 事务中的异常Link to this heading
在事务内部,当对 PostgreSQL 游标的调用引发异常(通常是 IntegrityError)时,同一事务中的所有后续 SQL 都将失败,出现错误消息 "current transaction is aborted, queries ignored until end of transaction block"。尽管在 PostgreSQL 中,基本使用 save() 不太可能引发异常,但更高级的使用模式可能会引发异常,例如保存带有唯一字段的对象、使用 force_insert/force_update 标志进行保存,或调用自定义 SQL。
有几种方法来从这种错误中恢复。
事务回滚Link to this heading
第一个选项是回滚整个事务。比如:
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 来控制回滚的程度。执行可能失败的数据库操作之前,你可以设置或更新保存点;这样,如果操作失败,你可以回滚单一的错误操作,而不是回滚整个事务。比如:
a.save() # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint_create()
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() 引发异常的情况下被撤销。