我有以下架构
ID(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;
以上解释
1)选择查询结果将用于锁定具有提供的引用 ID 的所有行,并且该结果用于某些业务逻辑
2) 更新查询以更新属于同一引用 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
此表用于高度并发和分布式应用程序(与相同的 ref_id 并行 100 个),这就是为什么我想通过在同一事务中选择然后更新来避免分布式锁。但是我面临着这个死锁错误,我没有知道为什么
显式锁定不起作用。
预期的行为是,如果具有相同参考 ID 的任何其他作业已获取锁定,则具有相同参考 ID 的任何其他作业都必须等待
帮助我找出我缺少的内容或其他解决方法。即使在显式锁定并处于事务内之后,我仍然不清楚为什么会发生死锁。
ORDER BY
来消除死锁的可能性。例如,在以下情况下会出现死锁:
但是如果两个进程提前同意锁定第 1 行,然后锁定第 2 行,那么这种情况就不会发生;一个进程仍会等待另一个进程,但另一个进程可以自由地继续。
更一般地,只要所有进程同意在获取锁时遵循相同的顺序,就可以保证至少其中一个进程始终取得进展;如果您只尝试获取比您已持有的锁“更高”的锁,那么持有“最高”锁的人将永远不会等待任何人。
排序需要明确,并且随着时间的推移保持稳定,因此生成的主键是理想的(即您应该
ORDER BY id
)。
select ... for update
和
update
时,正在创建 ShareLock。在我的例子中,也可能在上面的例子中,这是由于 Postgres 不喜欢使用“非主键”列作为“
select ... for update
”的标准。即使该列具有唯一约束,对该列的选择似乎也会锁定该表。我发现有两种方法可以解决这个问题:
将所选列设为主键 - 在我的例子中,它是唯一的文本参考代码。我从表中删除了 id bigint primary key
reference
列设为主键,死锁就停止了。在上述情况下,您可能需要在事务开始之前找到要选择的行的 ID。在伪代码/sql中:
$3 = (select * from table where ref_id = $1 and is_active is true)
begin transaction
select * from table where id = $3 for update;
UPDATE table set status = $1 where id =$2;
commit transaction