考虑以下示例:
-- 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;
不冒造成死锁的风险?
最后一个例子容易死锁。
重点是
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对同一张表的写访问可靠地坚持相同的顺序,就不会出现死锁(来自这种交互)。
相关: