Postgres 在带有子查询的 UPDATE 语句中忽略 LIMIT

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

我的 PostgreSQL 有一个奇怪的行为,我使用 docker image postgres:16-alpine 在本地运行性能测试,并遇到了意外的问题。 这是我使用的查询:

update fin_event_threads
   set status = 'READY_TO_PROCESS',
       last_updated = now(),
       owner = 'test'
 where 
       id = any (
                 select f.id
                   from fin_event_threads f
                  where f.status = 'PROCESSING'
                  order by f.last_updated
                  limit 1
                    for no key update skip locked
                ) 
      and status = 'PROCESSING'
returning id

这个查询更改了表中的所有记录,而不是一条记录。如果单独运行子查询,则仅返回 1 条记录。 这是请求的查询计划:

Update on fin_event_threads  (cost=1.17..2.38 rows=1 width=106) (actual time=0.965..4.143 rows=13 loops=1)
  ->  Nested Loop Semi Join  (cost=1.17..2.38 rows=1 width=106) (actual time=0.874..3.620 rows=13 loops=1)
        Join Filter: (fin_event_threads.id = "ANY_subquery".id)
        ->  Seq Scan on fin_event_threads  (cost=0.00..1.16 rows=1 width=10) (actual time=0.015..0.149 rows=13 loops=1)
              Filter: ((status)::text = 'PROCESSING'::text)
        ->  Subquery Scan on "ANY_subquery"  (cost=1.17..1.20 rows=1 width=32) (actual time=0.227..0.232 rows=1 loops=13)
              ->  Limit  (cost=1.17..1.19 rows=1 width=18) (actual time=0.209..0.213 rows=1 loops=13)
                    ->  LockRows  (cost=1.17..1.19 rows=1 width=18) (actual time=0.192..0.195 rows=1 loops=13)
                          ->  Sort  (cost=1.17..1.18 rows=1 width=18) (actual time=0.066..0.118 rows=7 loops=13)
                                Sort Key: f.last_updated
                                Sort Method: quicksort  Memory: 25kB
                                ->  Seq Scan on fin_event_threads f  (cost=0.00..1.16 rows=1 width=18) (actual time=0.013..0.559 rows=13 loops=1)
                                      Filter: ((status)::text = 'PROCESSING'::text)
Planning Time: 0.309 ms
Execution Time: 4.386 ms

为什么会这样?

我尝试使用

IN
代替
ANY
,并尝试使用简单的
for update skip locked
,但都没有帮助。

sql postgresql sql-update queue locking
1个回答
0
投票

假设

fin_event_threads.id
就是应有的
PRIMARY KEY

IN
ANY
构造对于必然返回 single 值的子查询来说都没有意义。解决这个问题,Postgres 就不会像查询计划所示那样迷失在嵌套循环中:

UPDATE fin_event_threads
SET    status = 'READY_TO_PROCESS'
     , last_updated = now()
     , owner = 'test'
WHERE  id = (   -- !!!
         SELECT f.id
         FROM   fin_event_threads f
         WHERE  f.status = 'PROCESSING'
         ORDER  BY f.last_updated
         LIMIT  1
         FOR NO KEY UPDATE SKIP LOCKED
         )
RETURNING id;

ANY
构造告诉Postgres在操作员
=
的右视上期望0-n值,这显然会误导查询规划者使用
Nested Loop
。在这样的星座中,锁定子句和
LIMIT
可以以不可预见的方式相互作用。 手册对此有警告。为了保持清晰,您宁愿在 CTE 中具体化子查询的选择。

但对于这种特殊情况,像您已有的子查询是最快的选择。只是不要将 Postgres 与误导性的

ANY
混淆。

参见:

此外,如果此查询的性能很重要,您应该有适当的 index 支持。查询计划中的

Seq Scan
告诉我你没有。最佳索引取决于未公开的设置细节。也许是部分索引:

CREATE INDEX ON fin_event_threads (last_updated) WHERE status = 'PROCESSING';
© www.soinside.com 2019 - 2024. All rights reserved.