依靠Postgres的死锁检测进行并发控制是否安全?

时间:2017-02-18 19:49:05

标签: postgresql deadlock database-deadlocks

我的应用程序偶尔遇到死锁,因为需要更新相同行但订单顺序不同的两个事务(例如,事务A更新行 X 然后 Y ,而事务B更新行 Y 然后 X )。

对于各种原因,解决避免这种死锁的传统方法 - 以一致的顺序锁定或更新行 - 不太理想。

由于我尝试执行的更新在其他方面是幂等的且与顺序无关的,因此在应用程序级别捕获这些偶然的死锁并重试事务是否安全合理?

例如:

def process_update(update):
    attempt = 0
    while attempt < 10:
        try:
            execute("SAVEPOINT foo")
            for row in update:
                execute("UPDATE mytable SET … WHERE …", row)
            execute("RELEASE SAVEPOINT foo")
            break
        except Deadlock:
            execute("ROLLBACK TO SAVEPOINT foo")
        attempt += 1
    raise Exception("Too many retries")

这是一个合理的想法吗?或者是否有与Postgres的死锁检测相关的成本可能会使其变得危险?

1 个答案:

答案 0 :(得分:2)

我为一个在同一个表上运行50到100个并发进程的系统做了大量的研究和实验。除了基本的死锁之外,还有许多事务失败。我的案例包括read committed和serializable事务。在应用程序级别处理此问题不会导致任何问题。幸运的是Postgres会立即失败,因此唯一的性能损失是应用程序,对数据库没有任何重要意义。

关键组件正在捕获每个type of error,知道哪些情况需要回滚,并且拥有exponential backoff for retries。我发现立即重试或静态睡眠时间导致进程只是反复死锁,造成一点多米诺骨牌效应,这是有道理的。

这是我的系统处理每个并发问题(伪代码)所需的完整逻辑:

begin transaction (either read committed or serializable)
while not successful and count < 5
    try 
        execute sql
        commit
    except
        if error code is '40P01' or '55P03'
            # Deadlock or lock not available
            sleep a random time (200 ms to 1 sec) * number of retries
        else if error code is '40001' or '25P02'
            # "In failed sql transaction" or serialized transaction failure
            rollback
            sleep a random time (200 ms to 1 sec) * number of retries
            begin transaction
        else if error message is 'There is no active transaction'
            sleep a random time (200 ms to 1 sec) * number of retries
            begin transaction
    increment count