`select for update` 当另一个进程更新该行中的外键并且 select 连接两个表时,不会返回更新的行

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

根据 PSQL docs 关于显式锁定:

SELECT FOR UPDATE 将等待在同一行上运行任何这些命令的并发事务,然后锁定并返回更新的行(如果该行被删除,则不返回任何行)。

但是,当我更新该行中的外键并且在 select 语句中与其他表使用连接时,它似乎没有返回更新的行。对于其他列,它按预期工作。看起来,当在并发事务中更新外键列时,更新的行不可见,并且其行为就像该行已被删除。

下面是我的简短示例。我希望两个(并发)事务都会更新该行。然而,只有其中一个这样做,第二个没有返回任何内容。

但是,当第一个事务中的更新语句更改为更新非 FK 列时,两个事务都可以选择该行,正如文档所预期的那样。

请注意,

select
已连接到
foo
表,即使该表未更新,但我实际上需要
foo
中的一些列,这只是一个最小的示例。如果没有 join,即使更新 fk,它也会按预期工作。 因此,此行为有两个重要条件 - 1. 更新 FK 列,2. 在 select 语句中使用 JOIN。

create table foo(pk int primary key);
create table bar(pk int, fk int references foo(pk), value int);
insert into foo values (1), (2), (3);
insert into bar values (1, 1, 1);

---- First transaction
begin;
select * from bar join foo on foo.pk = bar.fk where bar.pk = 1 for update of bar;
-- Now run select in the second transaction concurrently, see code below and then get back here.

update bar set fk = 2;
-- Update value column instead of fk and the second transaction will return row as expected.
-- update bar set value = 10;
commit;


---- Second (concurrent) transaction
begin;
select * from bar join foo on foo.pk = bar.fk where bar.pk = 1 for update of bar;
-- Continue in the first transaction.
-- Returns nothing after first transaction is commited; The row is not locked so we can't update it.
commit;

我在文档中错过了什么?对这种行为有什么解释吗?最好有参考资料。

它应该可以在干净的数据库中重现,配置中没有更改,读取已提交。

postgresql concurrency locking
1个回答
0
投票

您可以重写您的选择

create table foo(pk int primary key);
create table bar(pk int, fk int references foo(pk), value int);
insert into foo values (1), (2), (3);
insert into bar values (1, 1, 1);

---- First transaction
begin;
select * from bar  where bar.pk = 1 AND EXISTS( SELECT 1 FROM foo WHERE  foo.pk = bar.fk) for update of bar;
-- Now run select in the second transaction concurrently, see code below and then get back here.

update bar set fk = 2;
-- Update value column instead of fk and the second transaction will return row as expected.
-- update bar set value = 10;
commit;


---- Second (concurrent) transaction
begin;
select * from bar join foo on foo.pk = bar.fk where bar.pk = 1 for update of bar;
-- Continue in the first transaction.
-- Returns nothing after first transaction is commited; The row is not locked so we can't update it.
commit;
CREATE TABLE
CREATE TABLE
INSERT 0 3
INSERT 0 1
BEGIN
PK fk 价值
1 1 1
SELECT 1
UPDATE 1
COMMIT
BEGIN
PK fk 价值 PK
1 2 1 2
SELECT 1
COMMIT

小提琴

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