Postgres UPDATE with ORDER BY,怎么做?

时间:2017-06-20 18:17:14

标签: postgresql database-deadlocks

我需要对记录集合进行Postgres更新。我试图阻止压力测试中出现的死锁。

对此的典型解决方案是按照特定顺序更新记录,例如ID - 但似乎Postgres不允许ORDER BY进行更新。

假设我需要进行更新,例如:

UPDATE BALANCES WHERE ID IN (SELECT ID FROM some_function() ORDER BY ID);
当您同时运行200个查询时,

会导致死锁。怎么办?

我正在寻找一般解决方案,而不是UPDATE with ORDER BY

中的特定案例解决方法

感觉必须有比编写游标功能更好的解决方案。此外,如果没有更好的方法,那么光标功能如何最佳?逐个记录更新

2 个答案:

答案 0 :(得分:10)

据我所知,没有办法直接通过UPDATE声明完成此任务;保证锁定顺序的唯一方法是使用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
)

这具有在ID表上重复Balances索引查找的缺点。在您的简单示例中,您可以通过在锁定查询期间获取物理行地址(由ctid system column表示)并使用它来驱动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
))

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

不幸的是,规划人员只会在一组狭窄的案例中使用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

如果性能开销是个问题,那么您需要使用游标,这看起来像这样:

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
$$

答案 1 :(得分:2)

一般来说,并发很难。特别是有200个语句(我假设你不只是查询= SELECT)甚至是事务(实际上每个发出的语句都包含在一个事务中,如果它不在事务中)。

一般的解决方案概念是(这些)的组合:

  1. 要注意可能发生死锁,请在应用程序中捕获它们,检查class 40 40P01SELECT ... FOR UPDATE并重试该事务。

  2. 预订锁。使用READ COMMITED。尽可能地避免显式锁定。锁将强制其他事务等待锁释放,这会损害并发性,但可以防止事务进入死锁。在第13章中检查示例是否存在死锁。尤其是事务A等待B和B等待A(银行账户)的那个。

  3. 如果可能,请选择其他Error Codes,例如LOST UPDATE等较弱的Isolation Level。请注意READ COMMITED模式下的REPEATABLE READ。使用LOCK / USE A -- Transaction 1 LOCK / USE B -- Transaction 1 LOCK / USE C -- Transaction 1 -- D not used -- Transaction 1 -- A not used -- Transaction 2 LOCK / USE B -- Transaction 2 -- C not used -- Transaction 2 LOCK / USE D -- Transaction 2 预防他们。

  4. 在每个事务中使用相同的顺序编写带有锁的语句,例如按字母顺序按表名称。

    A B C D

    使用常规锁定顺序a1 b1 a2 a3 a4 b2 b3 。这样,事务可以以任何相对顺序交错,并且仍然有很好的机会不会死锁(取决于您的语句,您可能会遇到其他序列化问题)。事务的语句将按照它们指定的顺序运行,但可以是事务1运行它们的前2个,然后xact 2运行第一个,然后1个结束,最后xact 2结束。

    此外,您应该意识到在并发情况下不会以原子方式执行涉及多行的语句。换句话说,如果您有两个涉及多行的语句A和B,那么它们可以按以下顺序执行:

    EXPLAIN

    但不是b' s后面的一块。 这同样适用于具有子查询的语句。 您是否使用UPDATE BALANCES WHERE ID IN ( SELECT ID FROM some_function() FOR UPDATE -- LOCK using FOR UPDATE -- other transactions will WAIT / BLOCK temporarily on conc. write access ); 查看了查询计划?

    在您的情况下,您可以尝试

    LOST UPDATE

    如果可能的话,你也可以使用SELECT ... FOR UPDATE SKIP LOCK,它将跳过已锁定的数据以恢复并发,这会因为等待另一个事务释放锁(FOR UPDATE)而丢失。但是这不会对应用程序逻辑可能需要的锁定行应用UPDATE。所以稍后运行(参见第1点)。

    另请阅读LOST UPDATE关于SKIP LOCKED和。{ SKIP LOCKED关于SKIP LOCKED。在您的情况下,队列可能是一个想法,在 <section id="skills" class="skills-section"> <div> <h1>MY SKILLS</h1> </div> <ul class="chart" id="chart-line"> <li> <span style="height:5%; background: rgba(0, 102, 255, 0.80);" title="ActionScript"></span> </li> <li> <span style="height:70%; background: rgba(204, 51, 51, 0.80);" title="JavaScript"></span> </li> <li> <span style="height:50%; background: rgba(255, 186, 2, 0.80);" title="CoffeScript"></span> </li> <li> <span style="height:75%; background: rgba(0, 153, 102, 0.80);" title="HTML"></span> </li> <li> <span style="height:90%; background: rgba(0, 102, 255, 0.80);" title="HTML"></span> </li> <li> <span style="height:15%; background: rgba(204, 51, 51, 0.80);" title="HTML"></span> </li> <li> <span style="height:40%; background: rgba(255, 186, 2, 0.80);" title="HTML"></span> </li> <li> <span style="height:55%; background: rgba(0, 153, 102, 0.80);" title="HTML"></span> </li> </ul> <div class="button-down"> <a class="page-scroll" href="#projects"><button class="button-down-text">TESTO</button></a> </div> </section> <!-- Projects Section --> <!-- This is the begin of the section below --> <section id="projects" class="projects-section"> 引用中可以很好地解释,尽管关系DBMS并不是队列。

    HTH