Django 提供多种方式控制数据库事务。
Django 默认的事务行为是自动提交。除非事务正在执行,每个查询将会马上自动提交到数据库, 详见下文。
Django 自动使用事务或还原点,以确保需多次查询的 ORM 操作的一致性,特别是 delete() 和 update() 操作。
由于性能原因,Django 的 TestCase
类同样将每个测试用事务封装起来。
在 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()
只有在它被应用到视图时才会生效。
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
模型,如果在事务中更新 active
到 True
失败,那么这个片段确保最后的 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()
,这点尤其重要。
在 SQL 规范中,每一个 SQL 查询会启动事务,除非一个事务已经处于活动状态。然后必须显式地提交或回滚此事务。
这对开发者来说一直很头疼。为了减轻这个问题,大部分数据库提供了自动提交模式。当打开了自动提交,并且没有事务活动时,每一个 SQL 查询将被包含在自己的事务中。换句话说,每一个这种查询不仅会启动一个事务,而且事务也会被自动提交或回滚,这取决于查询是否成功。
PEP 249 (Python 数据库接口规范 v2.0)要求自动提交在初始时是关闭的。Django 会覆盖这个默认值并开启自动提交。
为了避免这种情况,你可以参考 deactivate the transaction management<deactivate-transaction-management> ,但并不推荐这样做。
你可以通过设置 AUTOCOMMIT 1
为 False
来对数据库完全禁用 Django 事务管理。如果你这么做了,Django 将不会启动自动提交,而且不会执行任何提交。你将获得底层数据库的常规行为。
这要求你显式地提交每一个事务,即使它们通过 Django 或第三方库启动。因此,这适用于当你想运行事务控制中间件或做一些非常奇怪的事情的情形。
有时你需要执行与当前数据库事务相关的操作,但前提是事务成功提交。例子可能包含 Celery 任务,邮件提醒或缓存失效。
Django provides the on_commit()
function to register callback functions
that should be executed after a transaction is successfully committed:
将任意函数(无参数)传递给 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 函数引发了未捕捉的异常,则同一个事务里的后续注册函数不会被运行。当然,这与你在没有 on_commit()
的情况下顺序执行函数的行为是一样的。
你的回调会在成功提交之后执行,因此在回调中的错误不会导致事务回滚。它们会在事务成功之后有条件的执行,但它们不是事务的一部分。对于预期的用例(邮件通知,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()
将导致错误。
Django 的 TestCase
类在事务中装饰每个测试,并且在每个测试后回滚事务,为了提供测试隔离。这意味着实际上没有提交任何事务,因此 on_commit()
回调不会运行。如果你需要测试 on_commit()
回调的结果,可以改用 TransactionTestCase
。
事务回滚钩子相比事务提交钩子更难实现,因为各种各样的情况都可能造成隐式回滚。
比如,如果数据库连接被删除,因为进程被杀而没有机会正常关闭,回滚钩子将不会运行。
解决方法很简单,与其在执行事务时(原子操作)进行某项操作,当事务执行失败后再取消这项操作,不如使用 on_commit()
来延迟该项操作,直到事务成功后再进行操作。毕竟事务成功后你才能确保之后的操作是有意义的。
Django provides a straightforward API in the django.db.transaction
module to manage the autocommit state of each database connection.
这些函数使接受一个 using
参数表示所要操作的数据库。如果未提供,则 Django 使用 "default"
数据库。
自动提交默认为开启,如果你将它关闭,自己承担后果。
一旦你关闭了自动提交, Django 将无法帮助你,数据库将会按照你使用的数据库适配器的默认行为进行操作。虽然适配器的标准经过了 PEP 249 详细规定,但不同适配器的实现方式并不总是一致的。你需要谨慎地查看你所使用的适配器的文档。
在关闭自动提交之前,你必须确保当前没有活动的事务,通常你可以执行 commit()
或者 rollback()
函数以达到该条件。
当一个原子 atomic()
事务处于活动状态时, Django 将会拒绝关闭自动提交的请求,因为这样会破坏原子性。
事务是指具有原子性的一系列数据库操作。即使你的程序崩溃,数据库也会确保这些操作要么全部完成要么全部都未执行。
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
中定义。
这些函数使接受一个 using
参数表示所要操作的数据库。如果未提供,则 Django 使用 "default"
数据库。
当一个原子 atomic()
事务处于活动状态时, Django 将会拒绝进行事务提交或者事务回滚,因为这样会破坏原子性。
保存点在事务中是标记物,它可以使得回滚部分乌市,而不是所有事务。 SQLite, PostgreSQL, Oracle, 和 MySQL (当使用 InnoDB 存储引擎) 后端提供了保存点。其他后端提供了保存点函数,但它们是空操作——它们实际上没有做任何事情。
如果你正在使用 Django 的默认行为——自动提交,保存点并不特别有用。尽管,一旦你用 atomic()
打开了一个事务,那么需要构建一系列的等待提交或回滚的数据库操作。如果发出回滚,那么会回滚整个事务。保存点有能力执行颗粒度级别的回滚,而不是由 transaction.rollback()
执行的完全回滚。
当嵌套了 atomic()
装饰器,它会创建一个保存点来允许部分提交或回滚。强烈推荐只使用 atomic()
而不是下面描述的函数,但它们仍然是公共 API 的一部分,而且没计划要弃用它们。
这里的每一个函数使用 using
参数,这个参数为应用的数据库名。如果没有 using
参数,那么会使用 "default"
数据库。
保存点由 django.db.transaction
: 中的三个函数来控制:
如果不支持保存点或数据库在自动模式时,这些函数不执行操作。
另外,还有一个实用功能:
下面的例子演示保存点的用法:
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()
块中执行此操作,那么整个块将仍然被回滚,因为它不知道你已经处理了较低级别的情况。为了防止此发生,你可以使用下面的函数控制回滚行为。
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.
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:
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.
注解
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.
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.
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.
8月 23, 2019