PostgreSQL:未使用的索引导致查询性能不佳?

Posted

技术标签:

【中文标题】PostgreSQL:未使用的索引导致查询性能不佳?【英文标题】:PostgreSQL: Bad query performance caused by unused index? 【发布时间】:2020-04-08 11:34:10 【问题描述】:

我们有一个查询来获取在特定时间段内发生变化的所有作业。根据所选时间段,性能从一天的

我发现如果时间段足够小,使用索引,查询速度很快。如果周期过大,则索引不被使用,查询变慢。

服务器运行版本为9.2

为什么会出现这种情况以及如何解决这个问题?

创建脚本:

CREATE TABLE IF NOT EXISTS "Job" 
(
    "id" serial PRIMARY KEY,
    "serial" TEXT NOT NULL
);
CREATE UNIQUE INDEX "index_Job_serial" ON "Job" ("serial" ASC);

CREATE TABLE IF NOT EXISTS "Property" 
(
    "id" serial PRIMARY KEY,
    "name" TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS "Timestamp"
(
    "id" serial PRIMARY KEY,
    "usSince1970" BIGINT NOT NULL ,
    "localTime" TEXT
);
CREATE INDEX "index_Timestamp_usSince1970" ON "Timestamp" USING btree ("usSince1970");

CREATE  TABLE IF NOT EXISTS "Changes" 
(
    "idJob" INTEGER  NOT NULL ,
    "idProperty" INTEGER  NOT NULL ,
    "idTimestamp" INTEGER  NOT NULL ,
    "value1" decimal(25,5),
    "value2" INTEGER ,
    "value3" TEXT ,
    PRIMARY KEY ("idJob", "idProperty", "idTimestamp") ,
    FOREIGN KEY ("idJob" ) REFERENCES "Job" ("id" ) ,
    FOREIGN KEY ("idProperty" ) REFERENCES "Property" ("id" ) ,
    FOREIGN KEY ("idTimestamp" ) REFERENCES "Timestamp" ("id" )
);
CREATE INDEX "index_Changes_idJob" ON "Changes" ("idJob" ASC);
CREATE INDEX "index_Changes_idProperty" ON "Changes" ("idProperty" ASC);
CREATE INDEX "index_Changes_idTimestamp" ON "Changes" ("idTimestamp" DESC);

快速查询:

-- fast query (1 day)
SELECT DISTINCT "idJob"
FROM "Changes" 
INNER JOIN "Timestamp" ON "Timestamp"."id" = "Changes"."idTimestamp" 
WHERE "Timestamp"."usSince1970" between 1584831600000000 and 1584745200000000 

-- explain
HashAggregate  (cost=26383.48..26444.33 rows=6085 width=4) (actual time=8.039..8.078 rows=179 loops=1)
  ->  Nested Loop  (cost=0.00..26368.26 rows=6085 width=4) (actual time=0.031..7.059 rows=6498 loops=1)
        ->  Index Scan using "index_Timestamp_usSince1970" on "Timestamp"  (cost=0.00..96.25 rows=2510 width=4) (actual time=0.022..0.514 rows=2671 loops=1)
              Index Cond: (("usSince1970" >= 1584745200000000::bigint) AND ("usSince1970" <= 1584831600000000::bigint))
        ->  Index Scan using "index_Changes_idTimestamp" on "Changes"  (cost=0.00..10.27 rows=20 width=8) (actual time=0.002..0.002 rows=2 loops=2671)
              Index Cond: ("idTimestamp" = "Timestamp".id)
Total runtime: 8.204 ms

慢查询:

-- slow query (7 days)
SELECT distinct "idJob"
FROM "Changes" 
INNER JOIN "Timestamp" ON "Timestamp"."id" = "Changes"."idTimestamp" 
WHERE "Timestamp"."usSince1970" between 1583708400000000 and 1584313200000000

-- explain
Unique  (cost=570694.82..571824.16 rows=92521 width=4) (actual time=8869.569..8930.545 rows=3695 loops=1)
  ->  Sort  (cost=570694.82..571259.49 rows=225867 width=4) (actual time=8869.568..8915.372 rows=260705 loops=1)
        Sort Key: "Changes"."idJob"
        Sort Method: external merge  Disk: 3552kB
        ->  Hash Join  (cost=4926.44..547518.97 rows=225867 width=4) (actual time=6325.494..8734.353 rows=260705 loops=1)
              Hash Cond: ("Changes"."idTimestamp" = "Timestamp".id)
              ->  Seq Scan on "Changes"  (cost=0.00..250722.43 rows=16238343 width=8) (actual time=0.004..2505.794 rows=16238343 loops=1)
              ->  Hash  (cost=3397.79..3397.79 rows=93172 width=4) (actual time=42.392..42.392 rows=107093 loops=1)
                    Buckets: 4096  Batches: 4  Memory Usage: 948kB
                    ->  Index Scan using "index_Timestamp_usSince1970" on "Timestamp"  (cost=0.00..3397.79 rows=93172 width=4) (actual time=0.006..20.831 rows=107093 loops=1)
                          Index Cond: (("usSince1970" >= 1583708400000000::bigint) AND ("usSince1970" <= 1584313200000000::bigint))
Total runtime: 8932.374 ms

提前致谢。

【问题讨论】:

与您的问题无关,但是:您应该真正避免使用那些可怕的带引号的标识符。他们的麻烦比他们的价值要多得多。 wiki.postgresql.org/wiki/… @a_horse_with_no_name:同意并注明:) 另外:没有理由将时间戳存储在 bigints 中。 而且,在慢查询中,估计是错误的。你有有效的统计数据吗? @wildplasser 上次统计重置是今天早上,是吗? 【参考方案1】:

慢查询处理更多的数据(100000 行与来自"Timestamp" 的 2500 行),所以它更慢也就不足为奇了。

您也可以强制 PostgreSQL 对慢速查询使用嵌套循环连接:

BEGIN;
SET LOCAL enable_hashjoin = off;
SET LOCAL enable_mergejoin = off;
SELECT ...;
COMMIT;

尝试一下,看看 PostgreSQL 是否正确,哈希连接是否真的很慢。

我怀疑 PostgreSQL 在这里做的事情是正确的,你提高性能的最好方法是增加work_mem

如果您愿意经常添加另一个索引并添加到​​VACUUM "Changes",则可以通过仅索引扫描获得更好的性能:

CREATE INDEX ON "Changes" ("idTimestamp") INCLUDE ("idJob");

在旧版本的 PostgreSQL 上

CREATE INDEX ON "Changes" ("idTimestamp", "idJob");

那么你最好删除现在不需要的索引"index_Changes_idTimestamp"

顺便说一句,使用驼峰式大小写和带引号的标识符让你的生活变得不必要地困难。

【讨论】:

感谢您的快速回复。强制服务器使用嵌套循环会导致“慢查询”在约 400 毫秒内完成。我需要更多的测试才能将它添加到应用程序中。将work_mem 增加到 100MB 使用 3765KB 确实稍微加快了查询到约 6 秒。昨天跑了VACUUM(来自Changes 的条目没有被删除)所以我认为这不会提高性能。 也许你的索引被缓存了,你应该增加effective_cache_size。或者,如果您有 SSD 存储,请减少 random_page_cost。两者都促进索引的使用。 VACUUM 不创建额外索引将无济于事。 您提供的索引创建语句被服务器拒绝,并在 'INCLUDE' 处出现语法错误。 哦,你有一个旧版本的 PostgreSQL(你没有告诉你的版本)。请参阅我的更新答案。 对不起,服务器的版本是9.2。更新了问题。【参考方案2】:

顺便说一句:您的查询相当于:


SELECT DISTINCT "idJob"
FROM "Changes" ch
WHERE EXISTS ( 
        SELECT * FROM "Timestamp"  ts
        WHERE ts.id = ch."idTimestamp" 
        AND ts."usSince1970" between 1584831600000000 and 1584745200000000 
        );

【讨论】:

以上是关于PostgreSQL:未使用的索引导致查询性能不佳?的主要内容,如果未能解决你的问题,请参考以下文章

关于哪些操作索引效果不佳或未使用

CTE 中的 SQL Server 视图导致性能不佳

sql PostgreSQL查询:完全未使用的索引

PostgreSql jsonb 列上的 GIN 索引未在查询中使用

索引视图以提高 SQL Server 上多个连接的性能

PostgreSQL 性能优化 短查询 覆盖索引,前缀索引,索引和排序