同一查询的本地 PostgreSQL 客户端的不同执行计划
Different execution plan for local PostgreSQL client for same query
出于某种原因,在本地和从我的 golang 后端应用程序执行的相同查询使用不同的执行计划。
事件流程:我遇到了从后端应用程序到数据库的低效计划 运行。在我的本地计算机上使用 PG 客户端连接到同一个 PostgreSQL 数据库后(使用与后端应用程序相同的用户),我重现了需要 >70ms
的查询。然后我使用我的本地 PG 客户端添加索引并重新执行查询,执行下降到 <1ms
.
然而,似乎 相同的查询一直从我的后端应用程序执行,并且仍然需要 >70ms
,这让我认为没有选择新添加的索引。我通过在本地 PG 客户端上使用 BEGIN;
和 COMMIT;
排除了后端应用程序查询在事务中运行的潜在原因,结果没有变化。
尽管我尽了最大的努力,但我还是无法理解这种异常行为的原因。也许这里有人有想法?这怎么可能?
-- The index I added:
CREATE INDEX CONCURRENTLY mytable_temp ON mytable (poolid, id asc) where NOT used;
-- The query:
BEGIN;
explain analyze UPDATE
mytable
SET
used = true
WHERE
id = (
SELECT
id
FROM
mytable
WHERE (poolid = 9
AND used = false
AND((startdate IS NULL
OR startdate <= '2021-12-03 18:12:16.952353384')
AND(enddate IS NULL
OR enddate >= '2021-12-03 18:12:16.952353384')))
ORDER BY
id ASC
LIMIT 1
FOR UPDATE SKIP LOCKED)
RETURNING
mytable.id,
mytable.poolid,
mytable.code,
mytable.startdate,
mytable.enddate,
mytable.attributes,
mytable.used,
mytable.importid,
mytable.created;
COMMIT;
计划(在本地执行时):
Update on mytable (cost=0.92..2.93 rows=1 width=132) (actual time=0.091..0.092 rows=1 loops=1)
InitPlan 1 (returns )
-> Limit (cost=0.43..0.49 rows=1 width=10) (actual time=0.069..0.069 rows=1 loops=1)
-> LockRows (cost=0.43..98599.17 rows=1699030 width=10) (actual time=0.068..0.069 rows=1 loops=1)
-> Index Scan using mytable_temp on mytable mytable_1 (cost=0.43..81608.87 rows=1699030 width=10) (actual time=0.065..0.065 rows=1 loops=1)
Index Cond: (poolid = 9)
" Filter: ((NOT used) AND ((startdate IS NULL) OR (startdate <= '2021-12-03 18:12:16.952353+00'::timestamp with time zone)) AND ((enddate IS NULL) OR (enddate >= '2021-12-03 18:12:16.952353+00'::timestamp with time zone)))"
-> Index Scan using mytable_pkey on mytable (cost=0.43..2.45 rows=1 width=132) (actual time=0.081..0.081 rows=1 loops=1)
Index Cond: (id = )
Planning Time: 0.146 ms
Execution Time: 0.120 ms
调查发现慢语句是prepared statement,前几次执行的很快。这几乎是一个错误估计的通用计划错误的证据。您可以执行以下操作:
改进 PostgreSQL 的估计,使其不选择通用计划(最好,但困难)
将此语句的 plan_cache_mode
设置为 force_custom_plan
(从 PostgreSQL v12 开始)
避免为此查询使用准备好的语句
出于某种原因,在本地和从我的 golang 后端应用程序执行的相同查询使用不同的执行计划。
事件流程:我遇到了从后端应用程序到数据库的低效计划 运行。在我的本地计算机上使用 PG 客户端连接到同一个 PostgreSQL 数据库后(使用与后端应用程序相同的用户),我重现了需要 >70ms
的查询。然后我使用我的本地 PG 客户端添加索引并重新执行查询,执行下降到 <1ms
.
然而,似乎 相同的查询一直从我的后端应用程序执行,并且仍然需要 >70ms
,这让我认为没有选择新添加的索引。我通过在本地 PG 客户端上使用 BEGIN;
和 COMMIT;
排除了后端应用程序查询在事务中运行的潜在原因,结果没有变化。
尽管我尽了最大的努力,但我还是无法理解这种异常行为的原因。也许这里有人有想法?这怎么可能?
-- The index I added:
CREATE INDEX CONCURRENTLY mytable_temp ON mytable (poolid, id asc) where NOT used;
-- The query:
BEGIN;
explain analyze UPDATE
mytable
SET
used = true
WHERE
id = (
SELECT
id
FROM
mytable
WHERE (poolid = 9
AND used = false
AND((startdate IS NULL
OR startdate <= '2021-12-03 18:12:16.952353384')
AND(enddate IS NULL
OR enddate >= '2021-12-03 18:12:16.952353384')))
ORDER BY
id ASC
LIMIT 1
FOR UPDATE SKIP LOCKED)
RETURNING
mytable.id,
mytable.poolid,
mytable.code,
mytable.startdate,
mytable.enddate,
mytable.attributes,
mytable.used,
mytable.importid,
mytable.created;
COMMIT;
计划(在本地执行时):
Update on mytable (cost=0.92..2.93 rows=1 width=132) (actual time=0.091..0.092 rows=1 loops=1)
InitPlan 1 (returns )
-> Limit (cost=0.43..0.49 rows=1 width=10) (actual time=0.069..0.069 rows=1 loops=1)
-> LockRows (cost=0.43..98599.17 rows=1699030 width=10) (actual time=0.068..0.069 rows=1 loops=1)
-> Index Scan using mytable_temp on mytable mytable_1 (cost=0.43..81608.87 rows=1699030 width=10) (actual time=0.065..0.065 rows=1 loops=1)
Index Cond: (poolid = 9)
" Filter: ((NOT used) AND ((startdate IS NULL) OR (startdate <= '2021-12-03 18:12:16.952353+00'::timestamp with time zone)) AND ((enddate IS NULL) OR (enddate >= '2021-12-03 18:12:16.952353+00'::timestamp with time zone)))"
-> Index Scan using mytable_pkey on mytable (cost=0.43..2.45 rows=1 width=132) (actual time=0.081..0.081 rows=1 loops=1)
Index Cond: (id = )
Planning Time: 0.146 ms
Execution Time: 0.120 ms
调查发现慢语句是prepared statement,前几次执行的很快。这几乎是一个错误估计的通用计划错误的证据。您可以执行以下操作:
改进 PostgreSQL 的估计,使其不选择通用计划(最好,但困难)
将此语句的
plan_cache_mode
设置为force_custom_plan
(从 PostgreSQL v12 开始)避免为此查询使用准备好的语句