我怎样才能避免Postgres中的这种三方僵局?

时间:2014-08-04 16:14:42

标签: sql postgresql deadlock

我在Postgres遇到了三方僵局,我真的不明白导致它发生了什么。日志消息是,

    Process 5671 waits for ExclusiveLock on tuple (33,12) of relation 31709 of database 16393; blocked by process 5652.
    Process 5652 waits for ShareLock on transaction 3382643; blocked by process 5670.
    Process 5670 waits for ExclusiveLock on tuple (33,12) of relation 31709 of database 16393; blocked by process 5671.
    Process 5671: UPDATE "user" SET
                    seqno = seqno + 1,
                    push_pending = true
                  WHERE user_id = $1
                  RETURNING seqno
    Process 5652: UPDATE "user" SET
                    seqno = seqno + 1,
                    push_pending = true
                  WHERE user_id = $1
                  RETURNING seqno
    Process 5670: UPDATE "user" SET
                    seqno = seqno + 1,
                    push_pending = true
                  WHERE user_id = $1
                  RETURNING seqno

(关系31709是"user"表。user_id在所有三个交易中都是相同的。)

这看起来不像你看到demonstrated in the documentation的普通死锁。我没有按顺序更新此表的多行。我怀疑RETURNING子句与它有关,但我不明白为什么。关于如何解决这个问题或进一步调试的任何想法?

在评论中更新Erwin的问题:这是Postgres 9.3。此事务中还有其他命令,但我不相信它们会触及“用户”表。表格中有一个触发器用updated_at更新current_timestamp()列:

CREATE OR REPLACE FUNCTION update_timestamp() RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = now();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

当我可以重现这个时,我会考虑获取交易的详细信息;我事后正在分析日志。

更新#2 :我使用LOCK_DEBUG重建Postgres并使用trace_locks=on运行,试图绕过锁定的顺序。

更新的死锁消息是:

Process 54131 waits for ShareLock on transaction 4774; blocked by process 54157.
Process 54157 waits for ExclusiveLock on tuple (1,16) of relation 18150 of database 18136; blocked by process 54131.

我可以清楚地看到ExclusiveLock上的阻止:

2014-08-05 10:35:15 EDT apiary [54131] 2/316 4773 LOG:  00000: LockAcquire: new: lock(0x10f039f88) id(18136,18150,1,16,3,1) grantMask(0) req(0,0,0,0,0,0,0)=0 grant(0,0,0,0,0,0,0)=0 wait(0) type(ExclusiveLock)
2014-08-05 10:35:15 EDT apiary [54131] 2/316 4773 LOG:  00000: GrantLock: lock(0x10f039f88) id(18136,18150,1,16,3,1) grantMask(80) req(0,0,0,0,0,0,1)=1 grant(0,0,0,0,0,0,1)=1 wait(0) type(ExclusiveLock)
2014-08-05 10:35:15 EDT apiary [54157] 3/188 4774 LOG:  00000: LockAcquire: found: lock(0x10f039f88) id(18136,18150,1,16,3,1) grantMask(80) req(0,0,0,0,0,0,1)=1 grant(0,0,0,0,0,0,1)=1 wait(0) type(ExclusiveLock)
2014-08-05 10:35:15 EDT apiary [54157] 3/188 4774 LOG:  00000: WaitOnLock: sleeping on lock: lock(0x10f039f88) id(18136,18150,1,16,3,1) grantMask(80) req(0,0,0,0,0,0,2)=2 grant(0,0,0,0,0,0,1)=1 wait(0) type(ExclusiveLock)

(格式为date program [pid] virtualtxid txid msg

但是我没有看到创建ShareLock的位置,或者事务4773在事务4774上阻塞的原因。我在查询pg_locks表时看到类似的结果:总是等待一个事务另一个事务的ShareLock阻塞了第一个事务的元组。有关如何深入了解ShareLock的来源的任何建议吗?

更新3 :我需要更新LOCK_DEBUG_ENABLED内联函数,无条件地返回true以查看ShareLock创建。一旦我这样做,我开始看到他们的创作:

2014-08-05 12:53:22 EDT apiary [76705] 2/471 6294 LOG:  00000: LockAcquire: lock [6294,0] ExclusiveLock
2014-08-05 12:53:22 EDT apiary [76705] 2/471 6294 LOG:  00000: LockAcquire: new: lock(0x115818378) id(6294,0,0,0,4,1) grantMask(0) req(0,0,0,0,0,0,0)=0 grant(0,0,0,0,0,0,0)=0 wait(0) type(ExclusiveLock)
2014-08-05 12:53:22 EDT apiary [76705] 2/471 6294 LOG:  00000: GrantLock: lock(0x115818378) id(6294,0,0,0,4,1) grantMask(80) req(0,0,0,0,0,0,1)=1 grant(0,0,0,0,0,0,1)=1 wait(0) type(ExclusiveLock)
2014-08-05 12:53:22 EDT apiary [76706] 4/153 6295 LOG:  00000: LockAcquire: lock [6294,0] ShareLock
2014-08-05 12:53:22 EDT apiary [76706] 4/153 6295 LOG:  00000: LockAcquire: found: lock(0x115818378) id(6294,0,0,0,4,1) grantMask(80) req(0,0,0,0,0,0,1)=1 grant(0,0,0,0,0,0,1)=1 wait(0) type(ShareLock)
2014-08-05 12:53:22 EDT apiary [76706] 4/153 6295 LOG:  00000: WaitOnLock: sleeping on lock: lock(0x115818378) id(6294,0,0,0,4,1) grantMask(80) req(0,0,0,0,1,0,1)=2 grant(0,0,0,0,0,0,1)=1 wait(0) type(ShareLock)

但是我仍然不确定为什么要创建ShareLock,为什么6295(在这种情况下)必须等待6294。

2 个答案:

答案 0 :(得分:5)

这可能就是僵局的发生方式。每个表在用户表的user_id上都有一个外键。当您插入具有外键约束的表时,postgres需要锁定引用表的行,以确保在插入引用它的行时它不会被删除(并违反提交时的FK约束) )。这应该是一个共享锁。

看起来所有引用用户的表的插入/更新也会在主表上插入后更新用户表上的用户seq。这些更新需要独占锁并被任何不属于当前事务的共享锁阻止。如果两个同时发生,它们就会陷入僵局。

例如,同时插入media_size和source的两个事务可能会死锁,如下所示:

T1                                   T2
-----------------------------------------------------------------------
1. Insert media size
1a. Excl Lock media size row
1b. Shared Lock on user row (FK)
                                     2. Insert Source
                                     2a. Excl Lock source row
                                     2b. Shared lock on user row (FK)

3. Update user seq
3a. Excl Lock on user row (BLOCKS on 2b)
                                     4. Update user seq
                                     4a. Excl Lock on user row (Blocks on 1b)
5. Deadlock

我认为将更新用户seq步骤切换为第一步是有意义的,因为它会在尝试获取共享锁之前强制T1和T2阻塞(由于它已经有一个已排除的锁,因此它不会需要)。

答案 1 :(得分:4)

假设默认为transaction isolation level Read Committed

UPDATE语句始终锁定更新的行。尝试更新同一行的并发事务在第一个事务回滚或提交之前无法继续。

RETURNING子句与问题正交。

死锁也会在没有Process 5671的情况下发生。这只是排在同一行的另一个事务,介于两者之间。 Process 5670Process 5652是死锁,实际上,很可能是由于同一事务中的其他命令。不太可能的候选人将是桌面上的触发器。

尝试将事务分解为更小的部分,以更新相同的直接顺序中的表行。然后他们无法互锁。

外键

因为你在后面的评论中提到外键:那些也可以在僵局中发挥作用。 Postgres 9.3引入了新的锁定级别来解决这个问题:

FOR KEY SHARE and FOR NO KEY UPDATE

Details in this blog post by Michael Paquier.

哪个应该让FK不再是一个问题。不过还是不排除。

最新点发布

自版本9.3.0起,锁定机制已有许多小修复。升级到最新的版本,可能帮助。

查看当前锁

回复你的评论: 您可以在系统目录视图pg_locks中找到(大多数)当前持有的锁。在得出结论之前,请务必阅读手册以及更多内容。