Управляемый транзакцией блок завершился ожиданием фиксации / ОТКАТА

#django #transactions

#django #транзакции

Вопрос:

У меня есть функция просмотра:

 @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
  

РЕДАКТИРОВАТЬ: никакие другие исключения не генерируются для изменения пути кода.

Комментарии:

1. используете ли вы postgres? Это может быть актуально: здесь и здесь

2. Да, ABC определен, извините. Чрезмерная очистка исходного кода с энтузиазмом!

Ответ №1:

Получив аналогичную проблему и потратив на нее несколько часов, я выяснил, как отладить эту ситуацию.

По какой-то причине декоратор @transaction.commit_manually отключает исключения, возникающие в функции.

Временно удалите декоратор из вашей функции, теперь вы увидите исключение, исправьте его и верните декоратор обратно!

Комментарии:

1. Этот совет попал в точку! что декоратор @transaction.commit_manually устранял истинную проблему.

2. Вы также сэкономили мне много часов. Такое поведение очень странное. Я использую декоратор commit_on_success, и он ведет себя по-другому.

3. Странно то, что выполнение 1/0 показывает правильное исключение: (

4. Когда я удалил этот декоратор, мой код просто работает нормально! Никаких ошибок, ничего. Очистите фиксации! Но я обеспокоен, поскольку я не могу запустить этот код в производство, не проверив, прошла транзакция успешно или нет. Есть идеи?

5. Это такая глупая, бестолковая проблема, и это превращает ручную обработку транзакций в мусор (невозможно отладить какие-либо ошибки во время выполнения, разработка — это кошмар и т.д. и т.п.).

Ответ №2:

У меня была та же проблема. Единственным решением, которое я нашел, было использовать предложение 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
  

Не имеет смысла, но у меня это сработало.

Комментарии:

1. У меня это сработало. Кроме того, я обнаружил, что если бы я хотел выполнить откат как часть обычного потока (не исключение), то мне пришлось бы создать исключение (x = 1/0), чтобы убедиться, что я закончил в конечном итоге с откатом. Возможно, потому, что вся моя функция находится в try catch .

2. Это хорошее решение, но вместо использования переменной, которую commited вы могли бы использовать transaction.is_dirty() . если вы можете, проверьте промежуточное программное обеспечение django transaction, они делают то же самое.

Ответ №3:

У меня была такая же проблема, и я узнал, что даже если вы правильно закроете транзакцию вручную в случае исключений, если вы затем снова выполните запись в orm в области ручной транзакции, кажется, что транзакция каким-то образом повторно открывается и вызывает исключение транзакции.

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

Ответ №4:

еще одна причина, по которой вы можете видеть эту проблему, заключается в том, что у вас в системе несколько баз данных.

Я смог устранить эту ошибку с помощью

 @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')
  

Ответ №5:

Это всегда происходит, когда где-то в коде возникает необработанное исключение. В моем случае по какой-то причине исключение не было передано отладчику, что и вызвало у меня путаницу.

Комментарии:

1. Это то, что я подозреваю. Но если есть исключение, я не могу его увидеть, и я пытался! В конце концов я решил решить проблему другим способом транзакций, и с моим новым подходом я не вижу исключения, которое я ожидал увидеть.

Ответ №6:

У меня была похожая проблема, возможно, этот код отлично подходит для вас:

 @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,
    })
  

Ответ №7:

Как говорили другие люди, исключения, возникающие внутри оформленной функции, «теряются», потому что они перезаписываются 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)
  

Ответ №8:

Поместите свой код в блок try / except .В блоке except просто выполните transaction.rollback и зарегистрируйте объект исключения.

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

Комментарии:

1. Вы уверены, что это совместимо с вопросами, поднятыми принятым ответом?

Ответ №9:

У меня была такая же проблема, и я пробовал различные подходы. Вот что сработало для меня, но я не уверен, что это правильный способ сделать это. Измените свой оператор return на:

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

Профессионалы Django, правильный ли это подход?

Ответ №10:

При необходимости сделайте резервную копию базы данных и удалите таблицу ROLLBACK_TEST из своей базы данных.

 mysql> DROP TABLE `ROLLBACK_TEST`;