Django 数据库事务

为了更好的阅读体验,欢迎访问 作者博客原文

管理数据库事务

Django框架提供了好几种方式来控制和管理数据库事务。(以下Django框架会简化为Django,读者可自行脑补框架两字)


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)[source]

比如,你就可以上面这个该装饰来让视图方法不受事务控制。

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)[source]

原子性是数据库事务的一个属性。使用atomic,我们就可以创建一个具备原子性的代码块。一旦代码块正常运行完毕,所有的修改会被提交到数据库。反之,如果有异常,更改会被回滚。

atomic管理起来的代码块还可以内嵌到方法中。这样的话,即便内部代码块正常运行,如果外部代码块抛出异常的话,它也没有办法把它的修改提交到数据库中。

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

一旦把atomic代码块放到try/except中,完整性错误就会被自然的处理掉了,比如下面这个例子:

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()产生的更改也有效。需要注意的是,在调用handle_exception()之前,generate_relationships()中的修改就已经被安全的回滚了。因此,如果有需要,你照样可以在异常处理函数中操作数据库。

尽量不要在atomic代码块中捕获异常

因为当atomic块中的代码执行完的时候,Django会根据代码正常运行来执行相应的提交或者回滚操作。如果在atomic代码块里面捕捉并处理了异常,就有可能隐盖代码本身的错误,从而可能会有一些意料之外的不愉快事情发生。

担心主要集中在DatabaseError和它的子类(如IntegrityError)。如果这种异常真的发生了,事务就会被破坏掉,而Django会在代码运行完后执行回滚操作。如果你试图在回滚前执行一些数据库操作,Django会抛出TransactionManagementError。通常你会在一个ORM相关的信号处理器抛出异常时遇到这个行为。

捕获异常的正确方式正如上面atomic代码块所示。如果有必要,添加额外的atomic代码块来做这件事情。这么做的好处是:当异常发生时,它能明确地告诉你那些操作需要回滚,而那些是不需要的。

为了保证原子性,atomic还禁止了一些API。像试图提交、回滚事务,以及改变数据库连接的自动提交状态这些操作,在atomic代码块中都是不予许的,否则就会抛出异常。

atomic使用一个参数来指定数据库的名字。如果该参数没有设置值,Django就会使用系统默认的数据库。

下面是Django的事务管理代码:

  • 进入最外层atomic代码块时开启一个事务;
  • 进入内部atomic代码块时创建保存点;
  • 退出内部atomic时释放或回滚事务;
  • 退出最外层atomic代码块时提交或者回滚事务;

你可以将保存点参数设置成False来禁止内部代码块创建保存点。如果发生了异常,Django在退出第一个父块的时候执行回滚,如果存在保存点,将回滚到这个保存点的位置,否则就是回滚到最外层的代码块。外层事务仍然能够保证原子性。然而,这个选项应该仅仅用于保存点开销较大的时候。毕竟它有个缺点:会破坏上文描述的错误处理机制。

你也可以在autocommit被关闭的时候使用atomic。此时,即使是最外层的代码块,它也只使用保存点。

性能考虑
开启事务会消耗数据库服务器的性能。为了最大化减小这种开销,应该让事务尽可能的短。特别是当你把atomic()用在
Django的请求/响应周期之外的一个耗时的操作中时,这点就显得尤为重要了。

自动提交

为什么Django默认使用自动提交?

SQL的标准中指出,除非已经存在一个开启的事务,否则每个SQL查询都会开启一个新事务。这些事务后续必须被明确的提交或者回滚。

这对应用开发者来说并不是很方便。为了降低这种不便性,大部分数据库提供了自动提交模式。当自动提交开启并且没有开启的事务时,每个SQL查询会被自己的事务包裹起来。换言之,不仅仅是每个查询开启一个事务,而且该事务还会根据查询是否成功采取自动提交或者回滚操作。

……

为了更好的阅读体验,更多内容,欢迎访问 作者博客原文

猜你喜欢

转载自blog.csdn.net/ysjian_pingcx/article/details/51015988