事务管理块以挂起的COMMIT / ROLLBACK结束

时间:2011-04-13 10:27:13

标签: django transactions

我有一个视图功能:

@transaction.commit_manually
def xyz(request):
    if ABC:
        success = something()

        if success:
            status = "success"
            transaction.commit()

        else:
            status = "dataerrors"
            transaction.rollback()
    else:
        status = "uploadproblem"
        transaction.rollback()

    return render(request, "template.html", {
        'status': status,
    })

我相信每个代码路径都会以这种或那种方式结束事务。但是Django似乎在抱怨它没有。有什么想法吗?

Django Version:     1.3
Exception Type:     TransactionManagementError
Exception Value:    Transaction managed block ended with pending COMMIT/ROLLBACK

编辑:没有其他异常会改变代码路径。

10 个答案:

答案 0 :(得分:85)

在得到类似问题并浪费时间后,我想出了如何调试这种情况。

由于某种原因,@ transaction.commit_manually装饰器会使函数中出现异常。

暂时从你的函数中删除装饰器,你现在会看到异常,修复它并把装饰器放回去!

答案 1 :(得分:9)

我遇到了同样的问题。我找到的唯一解决方案是使用try / finally子句来确保在渲染之后发生提交。

@transaction.commit_manually
def xyz(request):
    committed = False
    try:
        if ABC:
            success = something()

            if success:
                status = "success"
                transaction.commit()
                committed = True

            else:
                status = "dataerrors"
                transaction.rollback()
                committed = True
        else:
            status = "uploadproblem"
            transaction.rollback()
            committed = True

        return render(request, "template.html", {
            'status': status,
        })
    finally:
        if not committed:
            transaction.rollback() # or .commit() depending on your error-handling logic

没有意义,但它对我有用。

答案 2 :(得分:2)

我遇到了同样的问题,并了解到即使您在异常的情况下手动正确关闭事务,如果您随后在手动事务范围内再次写入orm,它似乎以某种方式重新打开事务并导致事务异常。

            with transaction.commit_manually():
                try:
                    <exciting stuff>
                    transaction.commit()                        
                except Exception, e:
                    transaction.rollback()
                    o.error='failed' <== caused transaction exception

答案 3 :(得分:2)

您可能会遇到此问题的另一个原因是系统中有多个数据库。

我能够用

克服这个错误
@transaction.commit_manually(using='my_other_db')
def foo():
   try:
        <db query>
        transaction.commit(using='my_other_db')
   except:
        transaction.rollback(using='my_other_db')

答案 4 :(得分:1)

当代码中某处出现未处理的异常时,总会发生这种情况。在我的情况下,由于某种原因,调试器没有抛出异常,这是导致我混淆的原因。

答案 5 :(得分:1)

我有类似的问题,也许这段代码适合你:

@transaction.commit_on_success
def xyz(request):
    if ABC:
        success = something()

        if success:
            status = "success"

        else:
            status = "dataerrors"
            transaction.rollback()
    else:
        status = "uploadproblem"
        transaction.rollback()

    return render(request, "template.html", {
        'status': status,
    })

答案 6 :(得分:1)

正如其他人所说,在装饰函数中发生的异常会“丢失”,因为它们会被TransactionManagementError异常覆盖。

我建议扩展transaction.commit_manually装饰器。我的装饰师transaction_commit_manually在内部使用transaction.commit_manually装饰器;如果在装饰函数中发生异常,我的装饰器会捕获异常,执行transaction.rollback()并再次引发异常。因此,事务被正确清除,原始异常不会丢失。

def _create_decorator_transaction_commit_manually(using=None):
    def deco(f):
        def g(*args, **kwargs):
            try:
                out = f(*args, **kwargs)
            except Exception as e:
                if using is not None:
                    transaction.rollback(using=using)
                else:
                    transaction.rollback()
                raise e
            return out
        if using is not None:
            return transaction.commit_manually(using=using)(g)
        return transaction.commit_manually(g)
    return deco

def transaction_commit_manually(*args, **kwargs):
    """
    Improved transaction.commit_manually that does not hide exceptions.

    If an exception occurs, rollback work and raise exception again
    """
    # If 'using' keyword is provided, return a decorator
    if 'using' in kwargs:
        return _create_decorator_transaction_commit_manually(using=kwargs['using'])
    # If 'using' keyword is not provided, act as a decorator:
    # first argument is function to be decorated; return modified function
    f = args[0]
    deco = _create_decorator_transaction_commit_manually()
    return deco(f)

答案 7 :(得分:0)

我遇到了同样的问题并尝试了各种方法。这对我有用,但我不确定这是否是正确的方法。将您的退货声明更改为:

with transaction.commit_on_success():
    return render(request, "template.html", {
        'status': status,
    })

Django Pros,这是正确的方法吗?

答案 8 :(得分:0)

将代码放在try / except块中。除了块之外,只需执行transaction.rollback并记录异常对象。

@transaction.commit_manually
def xyz(xyz):
   try:
       some_logic
       transaction.commit()
    except Exception,e:
       transaction.rollback()
       print str(e)

答案 9 :(得分:0)

根据需要进行数据库备份,并从数据库中删除表ROLLBACK_TEST

mysql> DROP TABLE `ROLLBACK_TEST`;