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

Joe*_*haw 7 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
Run Code Online (Sandbox Code Playgroud)

(关系31709是"user"表.user_id在所有三个事务中都是相同的.)

这看起来不像你在文档中看到的那种普通的死锁.我没有按顺序更新此表的多行.我怀疑该RETURNING条款与它有关,但我不明白为什么.关于如何解决这个问题或进一步调试的任何想法?

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

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

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

更新#2:我重建了Postgres,LOCK_DEBUGtrace_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.
Run Code Online (Sandbox Code Playgroud)

我可以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)
Run Code Online (Sandbox Code Playgroud)

(格式是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)
Run Code Online (Sandbox Code Playgroud)

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

Erw*_*ter 5

假设默认事务隔离级别Read Committed

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

RETURNING子句与问题正交。

如果没有Process 5671同样的情况,僵局就会发生。这只是为同一行排队的另一个事务,介于两者之间。Process 5670并且Process 5652死锁,实际上,很可能是由于同一事务中的其他命令。不太可能的候选人是桌子上的触发器。

尝试将事务分成更小的部分,以相同的直接顺序更新表相同表的行。那么它们就不能互锁了。

外键

由于您在后面的评论中提到了外键:这些外键也可以在死锁中发挥作用。Postgres 9.3 引入了新的锁定级别来解决这个问题:

FOR KEY SHAREFOR NO KEY UPDATE

Michael Paquier 的这篇博文中的详细信息。

这应该使 FK 约束减少问题。不过还是不排除。

最新点发布

自 9.3.0 版以来,对锁定机制进行了许多小的修复。升级到最新版本,它可能会有所帮助。

查看当前锁

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


Jas*_*der 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阻塞(由于它已经有一个已排除的锁,因此它不需要).