同一查询的本地 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 开始)

  • 避免为此查询使用准备好的语句