使用 FOR UPDATE SKIP LOCKED 是子查询时限制锁定行数?

问题描述 投票:0回答:1

以这个 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)
sql database postgresql queue locking
1个回答
0
投票

在子查询中使用

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
输出看起来与此处讨论的输出惊人相同:

© www.soinside.com 2019 - 2024. All rights reserved.