以这个 UPDATE 语句为例:
UPDATE queue_messages
SET status = 'leased'
WHERE id = ANY(
SELECT id FROM queue_messages
WHERE status = 'pending'
ORDER BY id ASC
LIMIT 1
FOR UPDATE SKIP LOCKED
)
我检查了EXPLAIN,发现子查询结果在应用LIMIT 1之前就被锁定了:
-> Nested Loop
-> Seq Scan on queue_messages
-> Subquery Scan on sub [...] loops=11
-> Limit [...] rows=2
-> LockRows
-> Sort
-> Seq Scan on queue_messages
这是否意味着有不止一行被锁定?如何确保只锁定一行?
我尝试过这个,但在解释计划中没有看到
LockRows
:
WITH _queue_ids AS (
SELECT id FROM queue_messages
WHERE status = 'pending'
ORDER BY id ASC
LIMIT 1
) UPDATE queue_messages
SET status = 'leased'
WHERE id = ANY(SELECT id FROM _queue_ids FOR UPDATE SKIP LOCKED)
在子查询中使用
LIMIT 1
,将其包装到 ANY
构造中是没有意义的。这使得 Postgres 准备多个返回值,从而导致更昂贵的查询计划。使用普通的=
:
UPDATE queue_messages
SET status = 'leased'
WHERE id = (
SELECT id
FROM queue_messages
WHERE status = 'pending'
-- ORDER BY id -- do you even need that?
LIMIT 1
FOR UPDATE SKIP LOCKED
);
LIMIT 1
和 SKIP LOCKED
表示您正在寻找下一个空闲行。如果您要处理表直到没有留下“待处理”行,并且 any 下一个空闲行都很好,请省略 ORDER BY id
。生成更简单(更快)的查询计划。
此查询(带或不带
ORDER BY
)精确锁定 1 行(如果有)。即使对于您的原创也是如此! 说明书:
如果使用
,一旦有足够的行,锁定就会停止 返回以满足限制(但请注意,跳过的行LIMIT
将会被锁定)。OFFSET
我在 Postgres 16 上使用
EXPLAIN ANALYZE
进行了测试(小心,实际上执行了 UPDATE
!)我总是在 LockRows
行中看到单个锁定行,其中包含任何讨论的查询:
-> LockRows (cost=25.91..25.98 rows=6 width=10) (actual time=0.208..0.208 rows=1 loops=1)
在这个例子中,rows=6
只是规划器根据列统计估计必须访问的行数。 rows=1
是实际锁定的行数。
最后,我不认为 Postgres 在删除无用的
Nested Loop
构造后会计划 ANY
。
顺便说一句,您的
EXPLAIN
输出看起来与此处讨论的输出惊人相同: