bbo*_*ozo 11 postgresql database-deadlocks
我需要对记录集合进行Postgres更新,并且我正在尝试防止压力测试中出现的死锁.
对此的典型解决方案是按照特定顺序更新记录,例如ID - 但似乎Postgres不允许ORDER BY进行更新.
假设我需要进行更新,例如:
UPDATE BALANCES WHERE ID IN (SELECT ID FROM some_function() ORDER BY ID);
Run Code Online (Sandbox Code Playgroud)
同时运行200个查询时会导致死锁.该怎么办?
我正在寻找一个通用的解决方案,而不是像在使用ORDER BY的UPDATE中那样的特定于案例的解决方法
它认为,必须有比写一个光标功能更好的解决方案.此外,如果没有更好的方法,那么光标的最佳运行方式是什么?逐个记录更新
Nic*_*nes 13
据我所知,没有办法直接通过UPDATE
声明来实现这一点; 保证锁定顺序的唯一方法是使用a显式获取锁定SELECT ... ORDER BY ID FOR UPDATE
,例如:
UPDATE Balances
SET Balance = 0
WHERE ID IN (
SELECT ID FROM Balances
WHERE ID IN (SELECT ID FROM some_function())
ORDER BY ID
FOR UPDATE
)
Run Code Online (Sandbox Code Playgroud)
这具有ID
在Balances
表上重复索引查找的缺点.在您的简单示例中,您可以通过在锁定查询期间获取物理行地址(由ctid
系统列表示)来避免此开销,并使用它来驱动UPDATE
:
UPDATE Balances
SET Balance = 0
WHERE ctid = ANY(ARRAY(
SELECT ctid FROM Balances
WHERE ID IN (SELECT ID FROM some_function())
ORDER BY ID
FOR UPDATE
))
Run Code Online (Sandbox Code Playgroud)
(使用ctid
s 时要小心,因为值是瞬态的.我们在这里很安全,因为锁会阻止任何更改.)
不幸的是,规划人员只会ctid
在一小组案例中使用(您可以通过在EXPLAIN
输出中查找"Tid Scan"节点来判断它是否正常工作).要在单个UPDATE
语句中处理更复杂的查询,例如,如果您的新余额some_function()
与ID一起返回,则需要回退到基于ID的查找:
UPDATE Balances
SET Balance = Locks.NewBalance
FROM (
SELECT Balances.ID, some_function.NewBalance
FROM Balances
JOIN some_function() ON some_function.ID = Balances.ID
ORDER BY Balances.ID
FOR UPDATE
) Locks
WHERE Balances.ID = Locks.ID
Run Code Online (Sandbox Code Playgroud)
如果性能开销是个问题,则需要使用游标,如下所示:
DO $$
DECLARE
c CURSOR FOR
SELECT Balances.ID, some_function.NewBalance
FROM Balances
JOIN some_function() ON some_function.ID = Balances.ID
ORDER BY Balances.ID
FOR UPDATE;
BEGIN
FOR row IN c LOOP
UPDATE Balances
SET Balance = row.NewBalance
WHERE CURRENT OF c;
END LOOP;
END
$$
Run Code Online (Sandbox Code Playgroud)