Повторите попытку в тупике для 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

Ответы

Ответ 1

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

Я полностью отбросил большинство материалов ORM в пользу интерфейса SQLAlchemy нижнего уровня. Используя этот (или даже любой интерфейс 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")

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

Ответ 2

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

try: 

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

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

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