PostgreSQL:在事务中检测到死锁 SELECT FOR UPDATE

Abh*_*oni 6 postgresql transactions sql-update

我有以下架构

身份证(PK)| REF_ID | 活跃 | 地位

ID - 主键

我正在使用以下查询来选择和更新

BEGIN;    
select * from table where ref_id = $1 and is_active is true for update;
UPDATE table set status = $1 where id =$2;
END;
Run Code Online (Sandbox Code Playgroud)

以上说明

1) Select 查询结果将用于锁定所有提供 ref ID 的行,该结果用于某些业务逻辑

2) 更新查询以更新属于同一 ref ID 的行的STATUS

问题

postgres@machine ERROR:  deadlock detected
postgres@machine DETAIL:  Process 28297 waits for ShareLock on transaction 4809510; blocked by process 28296.
        Process 28296 waits for ShareLock on transaction 4809502; blocked by process 28297.
        Process 28297: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update
        Process 28296: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update 
postgres@machine ERROR:  deadlock detected
postgres@machine DETAIL:  Process 28454 waits for ShareLock on transaction 4810111; blocked by process 28384.
        Process 28384 waits for ShareLock on transaction 4810092; blocked by process 28297.
        Process 28297 waits for AccessExclusiveLock on tuple (113628,5) of relation 16817 of database 16384; blocked by process 28454.
        Process 28454: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update
        Process 28384: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update
        Process 28297: select * from jobs where ref_id ='a840a8bd-b8a7-45b2-a474-47e2f68e702d' and is_active is true for update
Run Code Online (Sandbox Code Playgroud)

该表用于高并发和分布式应用程序(100 个并行使用相同的 ref_id),这就是为什么我想通过在同一个事务中选择然后更新来避免分布式锁。但我面临这个死锁错误我不知道为什么显式锁定不起作用。

预期行为是具有相同引用 ID 的任何其他作业必须等待,如果具有相同引用 ID 的任何其他作业已获取锁

帮助我弄清楚我缺少什么或其他解决方法。即使在显式锁定并处于事务中之后,我仍然不清楚为什么会发生死锁。

Nic*_*nes 9

正如 Laurenz 所说,在这个简单的情况下,您应该能够ORDER BY在锁定查询中消除死锁的可能性。

例如,在以下情况下会出现死锁:

  • 进程 A 获取第 1 行的锁
  • 进程 B 获取第 2 行的锁
  • 进程 A 请求锁定第 2 行(并等待 B 释放它)
  • 进程 B 请求锁定第 1 行(并等待 A 释放它)

...此时,进程将永远相互等待(或者更确切地说,直到服务器注意到并杀死其中一个)。

但是如果两个进程都提前同意锁定第 1 行然后锁定第 2 行,那么这种情况就不会发生;一个进程仍会等待另一个进程,但另一个进程可以自由进行。

更一般地说,只要所有进程在获取锁时都同意遵循相同的顺序,就可以保证至少其中一个进程一直在取得进展;如果您只尝试获取比您已经持有的锁“更高”的锁,那么持有“最高”锁的人将永远不会等待任何人。

排序需要明确且随着时间的推移保持稳定,因此生成的主键是理想的(即您应该ORDER BY id)。

  • 谢谢。我懒得把它拼出来。 (2认同)