表 A 是:
id integer
version varchar
data jsonb (large data 1mb)
fkToBid integer (references B.id constraint)
表 B 是:
id integer
other...
进程正在积极地运行下面的两个更新,以任何顺序和任何事务之外。
表 A 中的更新记录有时会引用表 B 中的相同记录。此外,有时会更新相同的 A 记录。
UPDATE A.version WHERE A.id=:id
and
UPDATE A.data WHERE A.id=:id
为什么或可以出现这种僵局?是因为表 A 中的更新记录引用了表 B 中的同一行吗?这种僵局会不会有别的原因?
为什么我会在 B pk 索引上看到这些更新请求的AccessShareLock ?
Postgres 在检查父表中是否存在外键以进行插入或更新时会使用该锁。来自:
RI_FKey_check
_src/backend/utils/adt/ri_triggers.c
问题是为什么要检查外键关系,因为您的更新都不会影响外键。
最可能的答案是 Postgres 无法检测到更新不会影响关系。从
RI_FKey_fk_upd_check_required
同一源文件中:尽管代码注释说了什么,但此限制适用于更新两次的行,以及插入行然后更新的规定情况。
复制:
db<>小提琴演示
请注意,仅第二次更新说明包括:
(是的,Postgres 使用每行内部触发器来检查和执行 RI。)
在 Postgres 9.3 及更高版本上,此 RI 检查
FOR KEY SHARE
用于更好的并发性,但这并不意味着它是防死锁的。无论如何,尽管问题说明了什么,但似乎在一个命令中发送了多个更新。这似乎是让 Postgres不必要地执行产生父表锁的 FK 检查所必需的。
如果您有其他进程使用 访问父表
SELECT FOR UPDATE
,则可以尝试将其更改为“提高外键锁定的并发性”Postgres 9.3 补丁的主要作者在此 Stack Overflow 答案SELECT FOR NO KEY UPDATE
中的建议。想到的是我写的一篇关于 Uber 使用 PostgreSQL 然后离开它的旧帖子。
这是那个帖子:为什么 MySQL 可以同时处理多个更新而 PostgreSQL 不能?
在我的那篇旧文章中,我写了 PostgreSQL 如何
ctid
在更新列时更改每一行的内部 ID,即使列没有被索引。我在问题中看到表 A 和表 B 之间存在外键关系。
如果两个表同时更新,则父级将不得不等待子级更新,或者子级可能必须等待父级更新。等什么 ???对于每一行的
ctid
改变。该行为必须在一个方向(parent
->child
)或另一个(child
->parent
)上序列化。这是我最好的猜测。