Django - 使用事务原子回滚保存

Posted

技术标签:

【中文标题】Django - 使用事务原子回滚保存【英文标题】:Django - Rollback save with transaction atomic 【发布时间】:2016-04-16 06:49:31 【问题描述】:

我正在尝试创建一个保存对象的视图,但如果引发异常,我想 撤消 保存该对象。这是我尝试过的:

class MyView(View):

    @transaction.atomic
    def post(self, request, *args, **kwargs):
        try:
            some_object = SomeModel(...)
            some_object.save()

            if something:
                raise exception.NotAcceptable()
                # When the workflow comes into this condition, I think the previous save should be undone
                # What am I missing?

        except exception.NotAcceptable, e:
            # do something

我做错了什么?即使引发异常some_object 仍在数据库中。

【问题讨论】:

【参考方案1】:

Atomicity Documentation

总而言之,如果您的视图产生没有错误的响应,@transaction.atomic 将在数据库上执行事务。因为您自己捕获了异常,所以在 Django 看来,您的视图执行得很好。

如果捕获到异常,需要自己处理:Controlling Transactions

如果您需要在失败的情况下产生正确的 json 响应:

from django.db import SomeError, transaction

def viewfunc(request):
    do_something()

    try:
        with transaction.atomic():
            thing_that_might_fail()
    except SomeError:
        handle_exception()

    render_response()

【讨论】:

此视图适用于 API,因此我认为我需要处理任何可能的错误以提供适当的 json 响应。有没有办法用原子装饰器做到这一点? 不使用装饰器恕我直言,因为它处理函数之外的事务。上下文管理器的好例子! 你必须在 try.. 中有一个原子块。除了答案中的块。如果你愿意,你也可以在视图上使用 atomic 装饰器。 实际上,您似乎不应该使用装饰器。您可能希望响应根据保存的成功而有所不同。因此,您应该像我在示例中那样做,然后您可以轻松判断您是否成功。如果响应不依赖于执行是否成功,则使用装饰器并且不要捕获/处理异常(删除 try/except 块)。 不,如果你想捕获异常,你必须在 try.. except 块中使用with transaction.atomic。我的观点是,您可以同时使用@transaction.atomic 来查看视图。如果您希望 do_something() 和 try..except 块的结果在一个成功或失败的事务中,这仍然很有用。【参考方案2】:

但是,如果在一个用transaction.atomic修饰的函数中发生异常,那么你就没有什么可做的了,它会rollback automatically to the savepoint created by the decorator before running the your function,就像documented:

atomic 允许我们创建一个代码块,在其中保证数据库的原子性。如果代码块成功完成,则将更改提交到数据库。如果出现异常,则回滚更改。

如果异常在 except 块中被捕获,那么应该重新引发它以供 atomic 捕获并执行回滚,即:

    try:
        some_object = SomeModel(...)
        some_object.save()

        if something:
            raise exception.NotAcceptable()
            # When the workflow comes into this condition, I think the previous save should be undome
            # Whant am I missing?

    except exception.NotAcceptable, e:
        # do something
        raise  # re-raise the exception to make transaction.atomic rollback

另外,如果你想要更多的控制权,你可以手动回滚到previously set savepoint,即:

class MyView(View):
    def post(self, request, *args, **kwargs):
        sid = transaction.savepoint()
        some_object = SomeModel(...)
        some_object.save()

        if something:
            transaction.savepoint_rollback(sid)
        else:
            try:
                # In worst case scenario, this might fail too
                transaction.savepoint_commit(sid)
            except IntegrityError:
                transaction.savepoint_rollback(sid)

【讨论】:

这就是我的想法,这就是我以这种方式执行此功能的原因,但正如我在问题中所说,当引发异常时,对象仍在数据库事件中,是否有任何附加步骤使用保存点? 可能是因为异常被 except 块捕获,并没有重新引发,所以 atomic 认为它是成功执行的函数。 如果我没有发现错误,我无法给出好的回应。如何构建我的函数? 添加了保存点回滚的示例,但我认为上下文管理器在这种情况下可能更合适。 使用带有 try catch 的保存点不会在失败的情况下回滚任何事务。谁能告诉我在下面的链接中使用带有 try-catch 的保存点:***.com/questions/60314464/…【参考方案3】:

对我来说,这适用于 Django 2.2.5

首先在你的settings.py中

...

DATABASES = 
    'default': 
        'ENGINE': 'xxx',  # transactional db
        ...
        'ATOMIC_REQUESTS': True,
    

在你的函数中(views.py)

from django.db import transaction

@transaction.atomic
def make_db_stuff():

    # do stuff in your db (inserts or whatever)

    if success:
        return True
    else:
        transaction.set_rollback(True)
        return False

【讨论】:

ATOMIC_REQUESTS 设置为 True 会使您的 all 视图具有原子性 - 因此您应该将其设置为 true 并在要排除的视图上使用 @transaction.non_atomic_requests ,或者只是通过@transaction.atomic 单独将视图设置为原子视图。来源:docs.djangoproject.com/en/3.0/topics/db/transactions/…

以上是关于Django - 使用事务原子回滚保存的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 事务操作(事务的回滚与提交)

mysql事务与锁

触发器事件事务函数

Django 事务回滚 HTTP 错误响应

关于MySQL回滚机制

带有 MySQL 非事务性更改表的 Django 无法回滚