Django Transaction 托管块以挂起的 COMMIT/ROLLBACK 结束

Posted

技术标签:

【中文标题】Django Transaction 托管块以挂起的 COMMIT/ROLLBACK 结束【英文标题】:Django Transaction managed block ended with pending COMMIT/ROLLBACK 【发布时间】:2012-04-14 20:49:05 【问题描述】:

我有一个需要手动管理事务的视图函数,但是当我应用 @transaction.commit_manually 装饰器时,django 总是会引发以下异常。

从下面的代码跟踪中可以看出,事务在从视图返回之前就已提交。

我在 windows 和 linux 上都使用 sqlite 和 django 1.4。

下面是django_trace的输出,后面是异常。需要明确的是:无论我是否使用 django_trace,都会发生这种情况,并且当没有装饰器时,不会引发任何异常。这不是由“吞下”异常引起的。

请注意,下面的第 60 行在上下文处理器内,因此在 commit_manually-wrapped 视图之外。

01->mainapp.views:1321:         transaction.commit()
01->mainapp.views:1322:         return render_to_response('mainapp/templates/incorporate.html',
01->mainapp.views:1323:                                       RequestContext(request, form_params))
02-->mainapp.views:60:     transaction.rollback_unless_managed()
02-->mainapp.views:61:     return 'home_login_form': AuthenticationForm(request)
Traceback (most recent call last):
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\contrib\staticfiles\handlers.py", line 67, in __call__
    return self.application(environ, start_response)
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\contrib\staticfiles\handlers.py", line 67, in __call__
    return self.application(environ, start_response)
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\core\handlers\wsgi.py", line 241, in __call__
    response = self.get_response(request)
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\core\handlers\base.py", line 179, in get_response
    response = self.handle_uncaught_exception(request, resolver, sys.exc_info())
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\core\handlers\base.py", line 221, in handle_uncaught_exception
    return debug.technical_500_response(request, *exc_info)
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\core\handlers\base.py", line 111, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\Marcin\Documents\oneclickcos\oneclickcos\mainapp\decorators.py", line 26, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\db\transaction.py", line 209, in inner
    return func(*args, **kwargs)
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\db\transaction.py", line 203, in __exit__
    self.exiting(exc_value, self.using)
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\db\transaction.py", line 288, in exiting
    leave_transaction_management(using=using)
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\db\transaction.py", line 52, in leave_transaction_management
    connection.leave_transaction_management()
  File "C:\Users\Marcin\Documents\oneclickcos\lib\site-packages\django\db\backends\__init__.py", line 119, in leave_transaction_management
    raise TransactionManagementError("Transaction managed block ended with "
TransactionManagementError: Transaction managed block ended with pending COMMIT/ROLLBACK

为了清楚起见,我已经检查了有关此主题的其他问题,但他们没有解决我的问题。

【问题讨论】:

【参考方案1】:

参考the doc

如果你的视图改变了数据并且没有 commit() 或 rollback(),Django 会抛出一个 TransactionManagementError 异常。

所以您在事务中有更改,并且需要在最后一次返回之前回滚或提交。 transaction.rollback_unless_managed() 将不起作用,因为 commit_manually 中的事务是托管

【讨论】:

我忘了提到第 60 行在上下文处理器内部(因此在 commit_manually 包装函数之外)。这会有所作为吗? 另外,将该行切换到commit() 也无济于事:( 这实际上是我意识到问题与我的答案一样的关键。 不,不是。将 RequestContext 实例传递给 render_to_response 方法时会调用上下文处理器,该方法仍在 @commit_manually 中。此外,您可以检查 django/db/backends/__init__.py 中的rollback_unless_managed 函数,它甚至将事务的状态设置为“有更改”并需要手动回滚或提交 谢谢 - 这解释了为什么我的答案的改变是解决方案。【参考方案2】:

原来在模板渲染过程中,有数据库访问,所以通常的模式是这样的:

return render_to_response('mainapp/templates/incorporate.html',
                          RequestContext(request, form_params))

是问题的原因。我需要将其替换为:

retval = render_to_response('mainapp/templates/incorporate.html',
                                      RequestContext(request, form_params))
transaction.commit()
return retval

此外,其他 SO 答案显示事务管理装饰器隐藏所有异常,而是引发事务管理异常。不幸的是,诊断此问题的最简单方法是在没有装饰器的情况下运行,并查看是否发生异常,或者将整个视图包装在 try/except 中。

【讨论】:

【参考方案3】:

我遇到了这个问题,并通过使用transaction.commit_on_success 装饰上下文处理器来解决它,例如

@transaction.commit_manually
def my_view(request):
    ...
    transaction.commit()
    return render_to_response("template.html", context_instance=RequestContext(request, my_ctx))


@transaction.commit_on_success
def my_context_processor(request):
    ...

即使是数据库读取也需要提交/回滚(包括上下文处理器中的那些)https://docs.djangoproject.com/en/dev/topics/db/transactions/#requirements-for-transaction-handling

【讨论】:

我不确定这如何适用于我的情况。 如果导致问题的上下文处理器是您自己的,那么您可以用@transaction.commit_on_success 装饰它并将您的视图恢复为return render_to_response(...。通过这样做,您创建的任何未来视图都可以使用手动事务管理(不仅仅是您更改的那个)。 是的,但我不明白您为什么会断定上下文处理器是问题所在。我并不是说它不是,但还没有其他人做出这种诊断。 因为显然您的视图确实提交/回滚了事务(就像我的一样)但仍然存在错误,当我添加装饰器时错误消失了。甚至数据库读取也需要提交/回滚docs.djangoproject.com/en/dev/topics/db/transactions/…您自己说'请注意,下面的第 60 行在上下文处理器内部,因此在 commit_manually-wrapped 视图之外。' 对,但是当我修复视图内抛出的异常时,问题就消失了。我对此有一个答案。

以上是关于Django Transaction 托管块以挂起的 COMMIT/ROLLBACK 结束的主要内容,如果未能解决你的问题,请参考以下文章

假设分配给命令的连接位于本地挂起事务中,ExecuteReader 要求命令拥有事务。命令的 Transaction 属性尚未初始化

将托管控制托管为 CWnd 时应用程序挂起

进程挂起从托管代码中调用 AmsiScanBuffer

启动时暂停 Windows 10 应用商店应用

托管在 Windows 服务中的 WCF 服务在停止时挂起

Django 熊猫 AWS