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

问题描述 投票:0回答:2

我有以下架构

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 的任何其他作业都必须等待

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

postgresql transactions sql-update
2个回答
18
投票
正如 Laurenz 所说,在这个简单的情况下,您应该能够在锁定查询中使用

ORDER BY

 来消除死锁的可能性。

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

    进程 A 获取第 1 行的锁
  • 进程 B 获取第 2 行的锁
  • 进程 A 请求第 2 行的锁(并等待 B 释放它)
  • 进程 B 请求第 1 行的锁(并等待 A 释放它)
...此时,进程将永远相互等待(或者更确切地说,直到服务器注意到并杀死其中一个)。

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

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

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

ORDER BY id

)。


0
投票
我迟到了这里的问题,但我遇到了类似的问题,当我在同一事务中执行

select ... for update

update
 时,正在创建 ShareLock。在我的例子中,也可能在上面的例子中,这是由于 Postgres 不喜欢使用“非主键”列作为“
select ... for update”的标准。即使该列具有唯一约束,对该列的选择似乎也会锁定该表。我发现有两种方法可以解决这个问题:

将所选列设为主键 - 在我的例子中,它是唯一的文本参考代码。我从表中删除了
    id bigint primary key
  1. 列,并将

    reference

     列设为主键,死锁就停止了。

    在上述情况下,您可能需要在事务开始之前找到要选择的行的 ID。在伪代码/sql中:
  2. $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

© www.soinside.com 2019 - 2024. All rights reserved.