选择更新

tf7tbtn2  于 2021-07-24  发布在  Java
关注(0)|答案(1)|浏览(407)

我有几个问题要问。首先,用 FOR UPDATE 锁定:

SELECT f.source_id FROM files AS f WHERE
    f.component_id = $1 AND
    f.archived_at IS NULL
FOR UPDATE

接下来是一个更新查询:

UPDATE files AS f SET archived_at = NOW()
WHERE
hw_component_id = $1 AND
f.source_id = ANY($2::text[])

还有一个插页:

INSERT INTO files AS f (
    source_id,
    ...
)
VALUES (..)
ON CONFLICT (component_id, source_id) DO UPDATE
SET archived_at = null,
is_valid = excluded.is_valid

我有两个应用程序示例,有时会在postgresql日志中看到死锁错误:

ERROR:  deadlock detected
DETAIL:  Process 3992939 waits for ShareLock on transaction 230221362; blocked by process 4108096.
Process 4108096 waits for ShareLock on transaction 230221365; blocked by process 3992939.
Process 3992939: SELECT f.source_id FROM files AS f WHERE f.component_id = $1 AND f.archived_at IS NULL FOR UPDATE
Process 4108096: INSERT INTO files AS f (source_id, ...) VALUES (..) ON CONFLICT (component_id, source_id) DO UPDATE SET archived_at = null, is_valid = excluded.is_valid
CONTEXT:  while locking tuple (41116,185) in relation \"files\"

我想可能是 ON CONFLICT DO UPDATE 语句,该语句可以更新未被前一个语句锁定的行 SELECT FOR UPDATE 但我不明白你怎么能 SELECT ... FOR UPDATE 如果查询是事务中的第一个查询,则会导致死锁。前面没有查询。可以 SELECT ... FOR UPDATE 语句锁定几行,然后等待条件中的其他行被解锁?

qxgroojn

qxgroojn1#

SELECT FOR UPDATE 无法防止死锁。它只是锁定行。锁是按照 ORDER BY ,或在没有 ORDER BY . 防止死锁的最佳方法是在整个事务中以一致的顺序锁定行,并且在所有并发事务中都这样做。或者,正如手册所说:
针对死锁的最佳防御通常是通过确保所有使用数据库的应用程序以一致的顺序获取多个对象上的锁来避免死锁。
否则,可能会发生这种情况(第1行、第2行。。。是否根据虚拟磁盘(一致的顺序)对行进行编号:

T1: SELECT FOR UPDATE ...          -- lock row2, row3
        T2: SELECT FOR UPDATE ...  -- lock row4, wait for T1 to release row2 
T1: INSERT ... ON CONFLICT ...     -- wait for T2 to release lock on row4

--> deadlock

添加 ORDER BY 给你的 SELECT... FOR UPDATE 可能已经避免了你的僵局(它将避免上面演示的情况。)或者发生这种情况,您必须做更多:

T1: SELECT FOR UPDATE ...          -- lock row2, row3
        T2: SELECT FOR UPDATE ...  -- lock row1, wait for T1 to release row2 
T1: INSERT ... ON CONFLICT ...     -- wait for T2 to release lock on row1

--> deadlock

事务中的每件事都必须以一致的顺序发生才能绝对确定。
还有,你的 UPDATE 似乎不符合 SELECT FOR UPDATE . component_id <> hw_component_id . 打字错误?
也, f.archived_at IS NULL 不保证以后 SET archived_at = NOW() 只影响这些行。你必须加上 WHERE f.archived_at IS NULLUPDATE 排队(在任何情况下似乎都是个好主意?)
我想可能是 ON CONFLICT DO UPDATE 语句,该语句可以更新未被前一个语句锁定的行 SELECT FOR UPDATE .
只要向上插入( ON CONFLICT DO UPDATE )坚持一致的顺序,那不是问题。但这可能很难或不可能实施。
可以 SELECT ... FOR UPDATE 语句锁定几行,然后等待条件中的其他行被解锁?
是的,如上所述,锁是沿途获得的。它可能不得不中途停下来等待。

诺瓦特

如果所有这些仍然不能解决死锁,那么慢而可靠的方法就是使用可序列化的隔离级别。然后您必须为序列化失败做好准备,并在这种情况下重试事务。总的来说要贵得多。
或者可以加上 NOWAIT :

SELECT FROM files
WHERE  component_id = $1
AND    archived_at IS NULL
ORDER  BY id   -- whatever you use for consistent, deterministic order
FOR    UPDATE NOWAIT;

手册:
NOWAIT ,如果无法立即锁定所选行,则语句将报告错误,而不是等待。
你甚至可以跳过 ORDER BY 带的子句 NOWAIT 如果你不能与上级建立一致的秩序。
然后您必须捕获该错误并重试事务。类似于捕获序列化失败,但成本更低,可靠性更低。例如,多个事务仍然可以单独与它们的upsert互锁。但这种可能性越来越小。

相关问题