使用 SELECT FOR UPDATE 时的 Postgresql 死锁

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

考虑以下示例:

-- Transaction 1 -> T1
BEGIN;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
SELECT * FROM table1 WHERE id = 2 FOR UPDATE;
UPDATE table1 set col1 = 'abcd' where id = 1;
COMMIT;

-- Transaction 2 -> T2
BEGIN;
SELECT * FROM table1 WHERE id = 2 FOR UPDATE;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
UPDATE table1 set col1 = 'defg' where id = 2;
COMMIT;

在这个例子中,如果两个事务同时执行,很明显会发生死锁,因为如果 T1 锁定 id=1 的行,然后 T2 锁定 id=2 的行,则 T1 和 T2 都无法执行第二个 SELECT FOR UPDATE查询,我们有一个僵局。

现在,为了解决这个问题,我们可以按照相同的顺序执行 SELECT FOR UPDATE 查询:

-- Transaction 1 -> T1
BEGIN;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
SELECT * FROM table1 WHERE id = 2 FOR UPDATE;
UPDATE table1 set col1 = 'abcd' where id = 1;
COMMIT;

-- Transaction 2 -> T2
BEGIN;
SELECT * FROM table1 WHERE id = 1 FOR UPDATE;
SELECT * FROM table1 WHERE id = 2 FOR UPDATE;
UPDATE table1 set col1 = 'defg' where id = 2;
COMMIT;

我们解决了这个例子的死锁问题。

现在,我的问题是,如果你考虑以下类似的例子:

-- Transaction 1 -> T1
BEGIN;
SELECT * FROM table1 WHERE id IN (1, 2) FOR UPDATE;
UPDATE table1 set col1 = 'abcd' where id = 1;
COMMIT;

-- Transaction 2 -> T2
BEGIN;
SELECT * FROM table1 WHERE id IN (1, 2) FOR UPDATE;
UPDATE table1 set col1 = 'defg' where id = 2;
COMMIT;

最后一个例子会不会死锁?

换句话说:postgres 会同时自动锁定所有匹配 WHERE 条件的行吗?

如果是,我们可以also说WHERE子句顺序不算数吗?所以在T1中我们可以使用

SELECT * FROM table1 WHERE id IN (1, 2) FOR UPDATE;

在 T2 中我们可以使用

SELECT * FROM table1 WHERE id IN (2, 1) FOR UPDATE;

不冒造成死锁的风险?

sql postgresql concurrency transactions database-deadlocks
1个回答
0
投票

最后一个例子容易死锁。

重点是

IN
子句中的项目列表不一定强制要求行被锁定的顺序。你需要一个
ORDER BY
子句来做到这一点。或者像你已经成功尝试过的那样单独声明。

单独的语句冗长且更昂贵。所以:

BEGIN;
SELECT FROM table1
WHERE  id IN (1,2)
ORDER  BY id             -- !
FOR    UPDATE;

UPDATE table1 set col1 = 'abcd' WHERE id = 1;
COMMIT;

只要all对同一张表的写访问可靠地坚持相同的顺序,就不会出现死锁(来自这种交互)。

相关:

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