MySQL / SQLAlchemy死锁重试

27

我已经搜索了相当长一段时间,但找不到解决我的问题的方法。我们在项目中使用SQLAlchemy与MySQL,并多次遇到可怕的错误:

1213, 'Deadlock found when trying to get lock; try restarting transaction'。

在这种情况下,我们想尝试最多三次重新启动事务。

我已经开始编写一个装饰器来实现此功能,但我不知道如何在失败之前保存会话状态并重试同一事务?(因为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
我有帮到你吗?或者你找到了其他的解决方案。请分享任何结果。 - Valeriy Solovyov
2个回答

3
您无法通过外部来实现这一点。必须在内部支持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")

you do

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

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


树莓派,如果您的重试在另一个死锁中失败会发生什么?两次尝试是否足够,或者应该有某种指数级退避? - Josh
从经验来看,2或3次足以。但是这严重取决于您的工作量。执行3次就足以显著降低错误日志中的噪音。我在代码中没有看到指数级退避的必要,但是因为它具有复杂性,只有在绝对必要时才会添加它。 - pi.

1
你使用过这样的代码吗?
try: 

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

处理死锁的唯一方法是编写代码时预料到它们的出现。如果你的数据库代码编写良好,这通常并不难。通常可以在查询执行逻辑周围放置一个try/catch,并在错误发生时查找死锁。如果捕获到死锁,则通常需要尝试重新执行失败的查询。

有用的链接:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接