web-dev-qa-db-ja.com

インデックスは、テーブル全体でGROUP BY / aggregateを使用してクエリを高速化できますか(選択性なし)。

A、b、cの3つの列を持つテーブルがあるとします。

インデックスを使用して、このようなクエリを高速化できますか?

SELECT a,b,SUM(c)  # or AVG(c)
FROM table
GROUP BY a,b
ORDER BY a,b
;

上記の質問が肯定的である場合、どのタイプのインデックスをお勧めしますかこれはどのように機能しますか?

5
Moras

ありそうもない。 _GROUP BY_および_ORDER BY_は通常、ソートを伴います。ただし、この場合はHashAggregateが使用されます(テーブル全体を操作しているためと考えられます)。

_CREATE TABLE foo AS
SELECT x % 5 AS a, x % 10 AS b, x AS c
FROM generate_series(1,1e6) AS x;
_

HashAggregateプランでは、

_# EXPLAIN ANALYZE SELECT a,b,sum(c) FROM foo GROUP BY a,b ORDER BY a,b;
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=23668.04..23668.16 rows=50 width=14) (actual time=611.607..611.608 rows=10 loops=1)
   Sort Key: a, b
   Sort Method: quicksort  Memory: 25kB
   ->  HashAggregate  (cost=23666.00..23666.62 rows=50 width=14) (actual time=611.589..611.593 rows=10 loops=1)
         Group Key: a, b
         ->  Seq Scan on foo  (cost=0.00..16166.00 rows=1000000 width=14) (actual time=0.012..71.157 rows=1000000 loops=1)
 Planning time: 0.168 ms
 Execution time: 611.665 ms
_

インデックスを追加します...

_CREATE INDEX idx ON foo (a,b);
VACUUM FULL ANALYZE foo;
_

...それでも同じクエリプランが表示されます。したがって、HashAggregateを無効にします

_SET enable_hashagg = false;
_

そしてさらに試みる..

_# EXPLAIN ANALYZE SELECT a,b,sum(c) FROM foo GROUP BY a,b ORDER BY a,b;
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=0.42..61292.04 rows=50 width=14) (actual time=108.149..655.536 rows=10 loops=1)
   Group Key: a, b
   ->  Index Scan using idx on foo  (cost=0.42..53791.41 rows=1000000 width=14) (actual time=0.066..272.299 rows=1000000 loops=1)
 Planning time: 0.121 ms
 Execution time: 655.594 ms
(5 rows)
_

また、以前の611msに比べて655msの時間がかかります。

より速く必要ですか?

それが十分に高速でない場合(そして、100万行をグループ化して合計するのに611msは悪くありません)。次に、ワークロードで許可されている場合は _MATERIALIZED VIEW_ を使用できます(クエリがホットまたは更新頻度が低い場合)。

_CREATE MATERIALIZED VIEW foo2 AS
SELECT a,b,sum(c)
FROM foo
GROUP BY a,b
ORDER BY a,b;
_

これで、_TABLE foo2_のときのサブMS時間に到達しました。次に、_REFRESH MATERIALIZED VIEW foo2;_を実行してビューを更新します。または、トリガーを作成して別のテーブルを更新し、それをトリガーで更新することもできます。

集計される実際の列。

いくつかの例外がありますが、sum()はそれらの1つではありません。ほとんどの集合体は通常インデックスを必要としないため、インデックスを使用しません。例外は、順序固有の集計です(min()およびmax()など)。たとえば、_(a,b)_でインデックスを作成した後、sum(a)を実行すると、

_# EXPLAIN ANALYZE SELECT sum(a) FROM foo;
                                                     QUERY PLAN                                                     
--------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=18666.00..18666.01 rows=1 width=4) (actual time=287.063..287.063 rows=1 loops=1)
   ->  Seq Scan on foo  (cost=0.00..16166.00 rows=1000000 width=4) (actual time=0.015..85.435 rows=1000000 loops=1)
 Planning time: 0.098 ms
 Execution time: 287.104 ms
(4 rows)
_

まだseqスキャンを使用していることがわかります。sum(c)には、まったくインデックスがない同じプランが表示されます。これがキッカーです。

_# EXPLAIN ANALYZE SELECT min(a) FROM foo;
                                                              QUERY PLAN                                                              
--------------------------------------------------------------------------------------------------------------------------------------
 Result  (cost=0.48..0.49 rows=1 width=0) (actual time=0.041..0.041 rows=1 loops=1)
   InitPlan 1 (returns $0)
     ->  Limit  (cost=0.42..0.48 rows=1 width=4) (actual time=0.036..0.037 rows=1 loops=1)
           ->  Index Only Scan using idx on foo  (cost=0.42..56291.41 rows=1000000 width=4) (actual time=0.035..0.035 rows=1 loops=1)
                 Index Cond: (a IS NOT NULL)
                 Heap Fetches: 1
 Planning time: 0.171 ms
 Execution time: 0.080 ms
(8 rows)
_

min(a)とは異なり、sum(a)は順序付けを利用できるため、クエリプランナーは、無料ではないインデックススキャンにメリットがあることに気付きます。

(a、b、c)のインデックスを使用した証明

理由が何であれ、cの追加のインデックスが合計の目的では重要ではないという証拠を確認したい場合(上記を読んでもまだ理由がわからない場合は質問してください)、

_-- turn this back on we turned it off earlier
SET enable_hashagg = true;
DROP INDEX idx;
CREATE INDEX idx ON foo (a,b,c);
VACUUM FULL ANALYZE foo;
EXPLAIN ANALYZE SELECT a,b,sum(c) FROM foo GROUP BY a,b ORDER BY a,b;
                                                        QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=23668.04..23668.16 rows=50 width=14) (actual time=608.888..608.889 rows=10 loops=1)
   Sort Key: a, b
   Sort Method: quicksort  Memory: 25kB
   ->  HashAggregate  (cost=23666.00..23666.62 rows=50 width=14) (actual time=608.869..608.871 rows=10 loops=1)
         Group Key: a, b
         ->  Seq Scan on foo  (cost=0.00..16166.00 rows=1000000 width=14) (actual time=0.015..72.613 rows=1000000 loops=1)
 Planning time: 0.130 ms
 Execution time: 608.947 ms
(8 rows)
_

まったく改善なし。 hashaggを無効にしても、改善は見られません。

TLDR;

この特定の単純なユースケースでは、インデックスは重要ではありません。プランナーは最適な方法を選択します。

5
Evan Carroll