Postgres UPDATE with ORDER BY,怎么做?

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)

这具有IDBalances表上重复索引查找的缺点.在您的简单示例中,您可以通过在锁定查询期间获取物理行地址(由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)

(使用ctids 时要小心,因为值是瞬态的.我们在这里很安全,因为锁会阻止任何更改.)

不幸的是,规划人员只会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)

  • 多么美妙的答案——应该被框起来。 (3认同)