我的 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
,但都没有帮助。
假设
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';