Postgres:SELECT FOR UPDATE 在锁定释放后看不到新行

Posted

技术标签:

【中文标题】Postgres:SELECT FOR UPDATE 在锁定释放后看不到新行【英文标题】:Postgres: SELECT FOR UPDATE does not see new rows after lock release 【发布时间】:2019-07-03 00:51:25 【问题描述】:

尝试在我的应用程序中支持 PostgreSQL DB,发现这种奇怪的行为。

准备工作:

CREATE TABLE test(id INTEGER, flag BOOLEAN);
INSERT INTO test(id, flag) VALUES (1, true);

假设两个并发事务(Autocommit=false, READ_COMMITTEDTX1TX2

TX1:

UPDATE test SET flag = FALSE WHERE id = 1;
INSERT INTO test(id, flag) VALUES (2, TRUE);
-- (wait, no COMMIT yet)

TX2:

SELECT id FROM test WHERE flag=true FOR UPDATE;
-- waits for TX1 to release lock

现在,如果我在 TX1COMMIT,TX2 中的 SELECT 将返回空光标。

这对我来说很奇怪,因为 Oracle 和 MariaDB 中的相同实验导致选择新创建的行 (id=2)。

我在 PG 文档中找不到有关此行为的任何信息。 我错过了什么吗? 有什么办法可以强制 PG 服务器在获取锁后“刷新”语句可见性?

PS:PostgreSQL 版本 11.1

【问题讨论】:

【参考方案1】:

TX2 扫描表并尝试锁定结果。

扫描会从查询开始看到数据库的快照,因此它无法看到由并发修改插入(或以某种其他方式使其符合条件)的任何行,这些修改在 之后开始已拍摄快照。

这就是为什么您看不到带有id 2 的行的原因。

对于id 1,这也是正确的,因此扫描会找到该行。但是查询必须等到锁被释放。当这最终发生时,它会获取该行的最新提交版本并再次执行检查,因此该行也被排除在外。

此“EvalPlanQual”重新检查(使用 PostgreSQL 术语)仅针对在扫描期间找到但被锁定的行执行。在扫描期间甚至没有找到第二行,因此那里没有进行此类处理。

这有点奇怪,承认。但这不是错误,它只是 PostgreSQL 的工作方式。

如果您想避免此类异常,请使用REPEATABLE READ 隔离级别。然后你会在这种情况下得到一个序列化错误,并且可以重试事务,从而避免这样的不一致。

【讨论】:

以上是关于Postgres:SELECT FOR UPDATE 在锁定释放后看不到新行的主要内容,如果未能解决你的问题,请参考以下文章

Postgres:SELECT FOR UPDATE 在锁定释放后看不到新行

mysql select for update:未锁定以供读取

postgres 查询返回记录集的函数

django select_for_update 获取关系锁

使用 Postgres 函数作为 ActiveRecord 模型

提高查询速度:大 postgres 表中的简单 SELECT