重试MySQL / SQLAlchemy的死锁

时间:2014-05-16 14:32:54

标签: python mysql sqlalchemy database-deadlocks

我已经搜索了很长时间,但无法找到解决问题的方法。我们将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

2 个答案:

答案 0 :(得分: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")

如果发生死锁,它将自动重试。

答案 1 :(得分:1)

您使用过这样的代码吗?

try: 

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

真正处理死锁的唯一方法是编写您期望的代码   他们。如果您的数据库代码是,这通常不是很困难   写得好。通常你可以在查询周围放一个try / catch   执行逻辑并在发生错误时查找死锁。如果你   抓住一个,正常的做法就是尝试执行   再次查询失败。

有用的链接: