codecamp

Django4.0 数据库事务-管理数据库事务

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, durable=False)

原子性是数据库事务的定义属性。 ​atomic ​允许创建代码块来保证数据库的原子性。如果代码块成功创建,这个变动会提交到数据库。如果有异常,变动会回滚。
atomic ​块可以嵌套。在这个例子里,当内部块成功完成时,如果在稍后外部块里引发了异常,则仍可回滚到最初效果。

确保原子块始终是最外层的原子块有时很有用,确保在退出块时提交任何数据库更改而没有错误。 这称为持久性,可以通过设置​durable=True​来实现。 如果原子块嵌套在另一个块中,则会引发 ​RuntimeError​。

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()​ 的变化也会在这里,并且绑定到相同的事务。注意,任何试图在 ​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()​ ,这点尤其重要。


Django4.0 执行原生SQL查询-直接执行自定义SQL
Django4.0 数据库事务-自动提交
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

Django4.0 模型和数据库

Django4.0 处理HTTP请求

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }