在 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
现在我想知道:如果在SELECT
CTE 和之间UPDATE
,表被另一个查询更新,my_value1
因此发生变化,计算会my_addition
在发生时变得过时和错误UPDATE
。会不会出现这样的情况?还是 Postgres 自动设置隐式锁?
如果 Postgres 在这里没有魔法,我需要自己处理它:在 CTE 中做就足够了FOR UPDATE
吗SELECT
?
抱歉,如果我没有在这里说清楚:这不是我想“看到”那些并发修改,我想阻止它们,即一旦计算SELECT
完成,在完成之前没有其他查询可能修改该行UPDATE
。
在现实生活中,我在这里嘲笑的CASE WHEN my_value1 > 100 THEN 50 ELSE 10 END
是大约 20 行长,我在UPDATE
. 由于我是“不要重复自己”的忠实粉丝,我认为 CTE 是要走的路。或者有没有更好的方法来避免在UPDATE
没有 CTE 的情况下复制和粘贴?
Postgres 使用多版本模型(多版本并发控制,MVCC)。
在默认
READ COMMITTED
隔离级别下,每个单独的查询在查询开始运行的那一刻有效地看到数据库的快照。如果在两者之间提交并发事务,则后续查询(即使在同一事务中)也可以看到不同的快照。(加上迄今为止在同一事务中所做的事情。)但是,就CTE而言,所有子语句
WITH
都与外部语句同时执行,它们实际上看到了数据库的相同快照。为此,所有这些都被视为单个查询。所以,不,您不需要显式锁定来保持一致。
出于多种原因,将逻辑封装在函数中可能很方便,但这对并发性没有任何影响。另外:具有volatile函数的 CTE 永远不会内联。看:
A
SELECT
不锁定查询的行。Postgres 允许并发UPDATES
. 但UPDATE
锁定目标行。尝试写入的并发事务也必须等到锁定事务完成。如果您想禁止写入仅在您
UPDATE
的进程中被选中的行(列),您可能仍然需要锁定(或使用更严格的隔离级别)。可能FOR UPDATE
是锁,或者可能是更弱的锁。这取决于您在问题中明确隐瞒/不提供的细节和要求。此外(尽管您没有要求这样做),如果多个并发事务可能正在写入重叠行(一次多个),请务必遵守相同、一致的行顺序以避免死锁。
如果要防止并发语句在 CTE 选择的行更新之前对其进行修改,则需要
SELECT ... FOR NO KEY UPDATE
在 CTE 中使用。基于a_horse_with_no_name所说的:
将加法逻辑放入一个函数中,然后每次设置新值时调用该函数。这将从两个方面帮助您。
像这样的东西应该工作。