我们有一个带有简单表格的应用程序

given_entity{
    UUID id;
    TimeStamp due_time;
    TimeStamp process_time;
}

这是一个spring boot(1.2.5.RELEASE)应用程序,使用spring-data-jpa.1.2.5.RELEASE和hibernate-4.3.10.FINAL作为jpa提供程序。
我们有5个这个应用程序的实例,每个实例都有一个调度程序,每2秒钟运行一次,并在数据库中查询到那些截止到现在还有2分钟的到期时间尚未处理的行;
SELECT * FROM given_entity
WHERE process_time is null and due_time between  now() and NOW() - INTERVAL '2 minutes'
FOR UPDATE

要求上表中的每一行都由一个应用程序实例成功处理。
然后应用程序实例处理这些行,并在一个事务中更新其process_time字段。
这可能需要或不需要超过2秒,这是调度程序间隔。
此外,这个表上没有任何索引,只有PK索引。
值得注意的第二点是,这些实例可能会插入该表中的行,该表由客户机单独调用。
问题:在日志中,我看到来自postgresql的这条消息(很少,但它会发生)
ERROR: deadlock detected
Detail: Process 10625 waits for ShareLock on transaction 25382449; blocked by process 10012.
Process 10012 waits for ShareLock on transaction 25382448; blocked by process 12238.
Process 12238 waits for AccessExclusiveLock on tuple (1371,45) of relation 19118 of database 19113; blocked by process 10625.
Hint: See server log for query details.
Where: while locking tuple (1371,45) in relation "given_entity"

问题:
这是怎么发生的?
我查看了postgresql锁并搜索了互联网。我没有发现任何说明死锁只在一个简单的表上是可能的。
我也不能用test重现这个错误。

最佳答案

进程A尝试锁定第1行和第2行。同时,进程B尝试锁定第2行,然后锁定第1行。这就是引发僵局所需要的一切。
问题是行锁是以不确定的顺序获取的,因为SELECT以不确定的顺序返回其行。避免这种情况只是确保所有进程在锁定行时都同意一个顺序,即:

SELECT * FROM given_entity
WHERE process_time is null and due_time between  now() and NOW() - INTERVAL '2 minutes'
ORDER BY id
FOR UPDATE

在Postgres9.5+中,您可以简单地忽略被另一个进程使用FOR UPDATE SKIP LOCKED锁定的任何行。

10-04 10:44