Повторите попытку при тупике для MySQL / SQLAlchemy

Я искал довольно долгое время и не могу найти решение своей проблемы. Мы используем SQLAlchemy в сочетании с MySQL для нашего проекта и несколько раз сталкиваемся с ужасной ошибкой:

1213, 'Обнаружена тупиковая ситуация при попытке получить блокировку; попробуйте перезапустить транзакцию».

В этом случае мы хотели бы попытаться перезапустить транзакцию не более трех раз.

Я начал писать декоратор, который делает это, но я не знаю, как сохранить состояние сеанса до сбоя и повторить ту же транзакцию после него? (Поскольку SQLAlchemy требует отката всякий раз, когда возникает исключение)

Моя работа до сих пор,

def retry_on_deadlock_decorator(func):
    lock_messages_error = ['Deadlock found', 'Lock wait timeout exceeded']

    @wraps(func)
    def wrapper(*args, **kwargs):
        attempt_count = 0
        while attempt_count < settings.MAXIMUM_RETRY_ON_DEADLOCK:
            try:
                return func(*args, **kwargs)
            except OperationalError as e:
                if any(msg in e.message for msg in lock_messages_error) \
                        and attempt_count <= settings.MAXIMUM_RETRY_ON_DEADLOCK:
                    logger.error('Deadlock detected. Trying sql transaction once more. Attempts count: %s'
                                 % (attempt_count + 1))
                else:
                    raise
            attempt_count += 1
    return wrapper

person cp2587    schedule 16.05.2014    source источник
comment
Я помог тебе? Или вы нашли другое решение. Поделитесь любым результатом.   -  person Valeriy Solovyov    schedule 22.04.2016


Ответы (2)


Вы не можете сделать это с Session снаружи. Session должен поддерживать это внутри компании. Это потребует сохранения большого количества частного состояния, поэтому это может не стоить вашего времени.

Я полностью отказался от большей части ORM в пользу низкоуровневого интерфейса SQLAlchemy Core. Используя это (или даже любой интерфейс dbapi), вы можете тривиально использовать декоратор retry_on_deadlock_decorator (см. вопрос выше), чтобы создать оболочку db.execute, поддерживающую повторные попытки.

 @retry_on_deadlock_decorator
 def deadlock_safe_execute(db, stmt, *args, **kw):
     return db.execute(stmt, *args, **kw)

И вместо

 db.execute("UPDATE users SET active=0")

вы делаете

 deadlock_safe_execute(db, "UPDATE users SET active=0")

который автоматически повторит попытку, если произойдет взаимоблокировка.

person pi.    schedule 04.10.2016
comment
pi, что произойдет, если ваша повторная попытка не удастся в другой тупиковой ситуации? Действительно ли достаточно двух попыток или должен быть какой-то экспоненциальный откат? - person Josh; 26.03.2019
comment
Судя по опыту, 2-3 раз должно хватить. Хотя сильно зависит от вашей загруженности. 3 раза достаточно, чтобы значительно уменьшить шум в журнале ошибок. Я не видел необходимости в экспоненциальном отступлении в своем коде, но из-за его сложности я добавлял бы его только в случае крайней необходимости. - person pi.; 01.04.2019

Вы использовали такой код?

try: 

     Perform table transaction 
     break 
except: 
     rollback 
     delay 
     try again to perform table transaction 

Единственный способ по-настоящему справиться с взаимоблокировками — это написать свой код, который их ожидает. Как правило, это не очень сложно, если код вашей базы данных хорошо написан. Часто вы можете просто поместить try/catch вокруг логики выполнения запроса и искать взаимоблокировку при возникновении ошибок. Если вы поймаете один из них, нормально будет просто попытаться снова выполнить неудачный запрос.

Полезные ссылки:

person Valeriy Solovyov    schedule 24.02.2015