未使用但影响查询的索引

lio*_*ori 9 postgresql index execution-plan count index-tuning

我有一个 PostgreSQL 9.3 表,其中包含一些数字和一些附加数据:

CREATE TABLE mytable (
    myid BIGINT,
    somedata BYTEA
)
Run Code Online (Sandbox Code Playgroud)

该表目前有大约 10M 条记录,占用 1GB 磁盘空间。myid不连续。

我想计算 100000 个连续数字的每个块中有多少行:

SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
Run Code Online (Sandbox Code Playgroud)

这将返回大约 3500 行。

我注意到某个索引的存在显着加快了这个查询,即使查询计划根本没有提到它。没有索引的查询计划:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=1636639.92..1709958.65 rows=496942 width=8) (actual time=6783.763..8888.841 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Sort  (cost=1636639.92..1659008.91 rows=8947594 width=8) (actual time=6783.752..8005.831 rows=8947557 loops=1)
         Output: ((myid / 100000))
         Sort Key: ((mytable.myid / 100000))
         Sort Method: external merge  Disk: 157440kB
         ->  Seq Scan on public.mytable  (cost=0.00..236506.92 rows=8947594 width=8) (actual time=0.020..1674.838 rows=8947557 loops=1)
               Output: (myid / 100000)
 Total runtime: 8914.780 ms
(9 rows)
Run Code Online (Sandbox Code Playgroud)

指数:

db=> CREATE INDEX myindex ON mytable ((myid/100000));
db=> VACUUM ANALYZE;
Run Code Online (Sandbox Code Playgroud)

新的查询计划:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=281242.99..281285.97 rows=3439 width=8) (actual time=3190.189..3190.800 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Seq Scan on public.mytable  (cost=0.00..236505.56 rows=8947485 width=8) (actual time=0.026..1659.571 rows=8947557 loops=1)
         Output: (myid / 100000)
 Total runtime: 3190.975 ms
(5 rows)
Run Code Online (Sandbox Code Playgroud)

因此,查询计划和运行时明显不同(几乎是三倍),但都没有提到索引。这种行为在我的开发机器上完全可以重现:我经历了多次删除索引、多次测试查询、重新创建索引、再次测试查询多次的循环。这里发生了什么事?

jja*_*nes 6

创建表达式索引时,它会导致 PostgreSQL 收集有关该表达式的统计信息。有了这些统计信息,它现在可以准确估计查询将返回的聚合行数,从而做出更好的计划选择。

特别是在这种情况下,如果没有这些额外的统计信息,它认为哈希表会太大而无法放入 work_mem,因此它没有选择该方法。


Erw*_*ter 4

VACUUM ANALYZE在你的例子中有所不同。另外,正如@jjanes 提供的,功能索引的附加统计信息。根据文档:

pg_statistic还存储有关索引表达式值的统计数据。这些被描述为就好像它们是实际的数据列一样;特别是starelid引用索引。但是,不会为普通非表达式索引列创建任何条目,因为它与基础表列的条目是多余的。

然而,创建索引本身并不导致 Postgres 收集统计信息。尝试:

CREATE INDEX myindex ON mytable ((myid/100000));
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;
Run Code Online (Sandbox Code Playgroud)

在运行第一个ANALYZE(或VACUUM ANALYZE,或 autovacuum 守护进程启动)之前,不会返回任何内容。

ANALYZE mytable;
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;
Run Code Online (Sandbox Code Playgroud)

现在您将看到添加的统计信息。

由于无论如何都必须读取整个表,Postgres 将使用顺序扫描,除非它预计myid/100000切换的计算成本足够高,但事实并非如此。

如果索引比表小得多,并且满足仅索引扫描的前提条件,那么您唯一的机会是仅索引扫描。详细信息请参阅 Postgres Wiki手册

只要不使用该功能索引,添加统计数据带来的附带好处就很有限。如果表是只读的,成本会很低 - 但话又说回来,我们可能会立即看到仅索引扫描。

也许您还可以通过设置更高的统计目标来实现更好的查询计划mytable.myid。这只会产生很小的成本。更多的: