我是否需要在 UPDATE 的 CTE 中显式 FOR UPDATE 锁?

cis*_*cis 9 postgresql cte locking update postgresql-13

在 Postgres 13 中,我有一个经常更新的表。然而,更新查询相当复杂,并且多次使用相同的值。因此,使用 CTE 似乎是一件非常合乎逻辑的事情。

一个简化的示例如下所示:

WITH my_cte AS (
    SELECT
          my_id,
          CASE WHEN my_value1 > 100 THEN 50 ELSE 10 END AS my_addition     
    FROM my_table      
    WHERE my_id = $1
)
UPDATE my_table
        SET my_value1 = my_table.my_value1 + my_cte.my_addition,
            my_value2 = my_table.my_value2 + my_cte.my_addition
FROM my_cte
WHERE my_table.my_id = my_cte.my_id
Run Code Online (Sandbox Code Playgroud)

现在我想知道:如果在SELECTCTE 和 之间UPDATE,表被另一个查询更新,my_value1从而发生变化,那么当发生这种情况时,were 的计算my_addition就会变得过时且错误,会发生什么UPDATE。会出现这样的情况吗?或者 Postgres 是否自动设置隐式锁?

如果 Postgres 在这里没有魔法,我需要自己处理它:FOR UPDATESELECTCTE 中做就足够了吗?

抱歉,如果我没有在这里说清楚:我并不是想“看到”这些并发修改,我想阻止它们,即一旦计算完成SELECT,没有其他查询可能会修改该行,直到计算UPDATE完成。

在现实生活中,我在这里嘲笑的内容CASE WHEN my_value1 > 100 THEN 50 ELSE 10 END大约有 20 行长,我需要它在UPDATE. 因为我是“不要重复自己”的忠实粉丝,所以我认为 CTE 是正确的选择。或者是否有更好的方法来避免在UPDATE没有 CTE 的情况下复制和粘贴?

Erw*_*ter 10

Postgres 使用多版本模型(多版本并发控制,MVCC)。

在默认READ COMMITTED隔离级别下,每个单独的查询实际上都会看到查询开始运行时数据库的快照。如果在中间提交并发事务,后续查询(即使在同一事务中)也可以看到不同的快照。(加上迄今为止在同一笔交易中所做的事情。)

然而,就CTE而言,中的所有子语句都WITH与外部语句同时执行,它们实际上看到的是数据库的相同快照。为此,所有这些都被视为单个查询。

所以,,您不需要显式锁定来保持一致性。

由于多种原因,将逻辑封装在函数中可能很方便,但这对并发性没有任何影响。旁白:具有易失性函数的 CTE永远不会被内联。看:

ASELECT不锁定查询的行。Postgres 允许并发UPDATES。但UPDATE锁定目标行。并发事务也尝试写入,必须等到锁定事务完成。

如果您想禁止写入仅在正在进行时选择的行(列)UPDATE,您可能无论如何都需要锁定(或使用更严格的隔离级别)。也许是FOR UPDATE锁,或者可能是较弱的锁。这取决于您在问题中明确保留/不提供的细节和要求。

另外(尽管您没有要求这样做),如果多个并发事务可能写入重叠的行(一次多个行),请务必遵守相同、一致的行顺序避免死锁。这通常需要ORDER BY在锁定之前。

如果对此有贡献的列ORDER BY可能会被并发事务更新,您还需要添加NOWAIT以确保确定。锁定发生在ORDER BY. 如果并发事务更新其间的一行,Postgres 将等待该事务完成。如果提交,该行现在可能会以不同的方式排序,并且锁定将无序发生,从而重新引入死锁的可能性。这NOWAIT是确保READ COMMITTED隔离级别的唯一方法。或者使用更严格的隔离级别。REPEATABLE READ或者SERIALIZABLE无论如何都会引发序列化失败。