我有几个问题要问。首先,用 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
语句锁定几行,然后等待条件中的其他行被解锁?
1条答案
按热度按时间qxgroojn1#
SELECT FOR UPDATE
无法防止死锁。它只是锁定行。锁是按照ORDER BY
,或在没有ORDER BY
. 防止死锁的最佳方法是在整个事务中以一致的顺序锁定行,并且在所有并发事务中都这样做。或者,正如手册所说:针对死锁的最佳防御通常是通过确保所有使用数据库的应用程序以一致的顺序获取多个对象上的锁来避免死锁。
否则,可能会发生这种情况(第1行、第2行。。。是否根据虚拟磁盘(一致的顺序)对行进行编号:
添加
ORDER BY
给你的SELECT... FOR UPDATE
可能已经避免了你的僵局(它将避免上面演示的情况。)或者发生这种情况,您必须做更多:事务中的每件事都必须以一致的顺序发生才能绝对确定。
还有,你的
UPDATE
似乎不符合SELECT FOR UPDATE
.component_id
<>hw_component_id
. 打字错误?也,
f.archived_at IS NULL
不保证以后SET archived_at = NOW()
只影响这些行。你必须加上WHERE f.archived_at IS NULL
到UPDATE
排队(在任何情况下似乎都是个好主意?)我想可能是
ON CONFLICT DO UPDATE
语句,该语句可以更新未被前一个语句锁定的行SELECT FOR UPDATE
.只要向上插入(
ON CONFLICT DO UPDATE
)坚持一致的顺序,那不是问题。但这可能很难或不可能实施。可以
SELECT ... FOR UPDATE
语句锁定几行,然后等待条件中的其他行被解锁?是的,如上所述,锁是沿途获得的。它可能不得不中途停下来等待。
诺瓦特
如果所有这些仍然不能解决死锁,那么慢而可靠的方法就是使用可序列化的隔离级别。然后您必须为序列化失败做好准备,并在这种情况下重试事务。总的来说要贵得多。
或者可以加上
NOWAIT
:手册:
与
NOWAIT
,如果无法立即锁定所选行,则语句将报告错误,而不是等待。你甚至可以跳过
ORDER BY
带的子句NOWAIT
如果你不能与上级建立一致的秩序。然后您必须捕获该错误并重试事务。类似于捕获序列化失败,但成本更低,可靠性更低。例如,多个事务仍然可以单独与它们的upsert互锁。但这种可能性越来越小。