web-dev-qa-db-ja.com

インデックススキャンの方が良いのに、なぜPostgreSQLは遅いSeqスキャンを選択するのですか?

Postgres 9.6 DBに次のテーブルがあります。

create table baseDimensions(
    id serial not null primary key,
    dimension1 date not null,
    dimension2 int not null,
    dimension3 text not null,
    -- ...
    dimension10 boolean not null,
    unique(dimension1, dimension2, ..., dimension10)
);

create table interestingData(
    baseDimensionId int not null references baseDimensions(id),
    subdimension1 int not null,
    subdimension2 boolean not null,
    -- ...
    value1 int not null,
    value2 bigint not null,
    -- ...
    primary key(baseDimensionId, subdimension1, subdimension2)
)

だから本文:私はたくさんのディメンションを持っています(たとえば、テーブルが小売店の販売用である場合、ディメンションは販売日、customerId、顧客がリピート顧客であるかどうか、顧客のジャケットの色である可能性があります着用など)およびそれらの寸法のいくつかの値(小売りの例にとどまる場合:値は顧客が支払った金額である場合があります)。複数の「interestingData」テーブルがありますが、すべて10の基本ディメンションを共有しているため、ディスク領域を節約するために、これらの基本ディメンションを別のテーブルに抽出しました。

一意のインデックスの列の順序を意図的に選択して、典型的なフィルター条件が左端になるようにしました。ほとんどの場合、ディメンション1でデータをフィルターします。

次に、特定の日の平均value1dimension1ごとのdimension3)を知りたいと思います。これは、次のクエリで回答できます。

select dimension3, avg(value1)
from baseDimensions b
join interestingData c on b.id = c.baseDimensionId
where b.dimension1 = '2016-08-20'::date
group by dimension3

このクエリの説明と分析は次のようになります。

HashAggregate  (cost=1141685.72..1141686.14 rows=28 width=41) (actual time=55824.222..55827.068 rows=3358 loops=1)
  Group Key: b.dimension3
  ->  Hash Join  (cost=63915.68..1139740.91 rows=259308 width=41) (actual time=9956.393..55684.492 rows=267004 loops=1)
        Hash Cond: (c.basedimensionid = b.id)
        ->  Seq Scan on interestingData c  (cost=0.00..628619.84 rows=28705684 width=20) (actual time=0.007..31909.112 rows=28705684 loops=1)
        ->  Hash  (cost=62303.57..62303.57 rows=83369 width=29) (actual time=93.587..93.587 rows=81101 loops=1)
              Buckets: 131072  Batches: 2  Memory Usage: 3565kB
              ->  Index Scan using baseDimensions_dimensions_key on baseDimensions b  (cost=0.56..62303.57 rows=83369 width=29) (actual time=0.021..59.422 rows=81101 loops=1)
                    Index Cond: (dimension1 = '2016-08-20'::date)
Planning time: 0.232 ms
Execution time: 55827.909 ms

55秒では、このクエリは遅く、私の解釈は、その理由は、interestingDataテーブルのシーケンススキャンにあるためです。しかし、クエリプランナーはここにありますか?インデックスの使用はさらに遅くなりますか?知りたかったので、set enable_seqscan=falseでシーケンススキャンを強制的に無効にしようとしました。新しいExplain + analyzeは次のとおりです。

HashAggregate  (cost=1790548.21..1790548.63 rows=28 width=41) (actual time=1023.655..1025.661 rows=3358 loops=1)
  Group Key: b.dimension3
  ->  Nested Loop  (cost=1.12..1788603.40 rows=259308 width=41) (actual time=0.034..848.152 rows=267004 loops=1)
        ->  Index Scan using baseDimensions_dimensions_key on baseDimensions b  (cost=0.56..62303.57 rows=83369 width=29) (actual time=0.019..76.750 rows=81101 loops=1)
              Index Cond: (dimension1 = '2016-08-20'::date)
        ->  Index Scan using interestingData_pkey on interestingData c  (cost=0.56..20.08 rows=63 width=20) (actual time=0.003..0.007 rows=3 loops=81101)
              Index Cond: (basedimensionid = b.id)
Planning time: 0.250 ms
Execution time: 1026.478 ms

そして、すごい...クエリは突然50倍以上の速さです!

ただし 本番環境でset enable_seqscan = falseを使用することは想定されていませんでは、クエリプランナーのパフォーマンスがそれほどひどいのはなぜですか。どうすればよいですか

統計

状況についてのより良いアイデアを得るためのテーブル統計(新しいデータが毎日到着するため、テーブルは将来大きく成長するでしょうが、現在は古いデータを削除する予定はありません):

select
    count(*) total,
    count(distinct dimension1) distinctDays,
    min(dimension1) startDate,
    max(dimension1) endDate
from baseDimensions

  total    | distinctdays | startdate  |  enddate   
-----------+--------------+------------+------------
 9,229,054 |          171 | 2016-07-17 | 2017-01-19
select
    count(*) total
from interestingData ;

   total   
------------
 28,705,684

バリアント

Ypercube(コメント内)のリクエストにより、(dimension1、id、dimension3)にインデックスを持つバリアント:

HashAggregate  (cost=1141043.72..1141044.14 rows=28 width=41) (actual time=45213.910..45217.416 rows=3358 loops=1)
  Group Key: b.dimension3
  ->  Hash Join  (cost=63273.68..1139098.91 rows=259308 width=41) (actual time=6871.808..45082.855 rows=267004 loops=1)
        Hash Cond: (combined.basedimensionid = b.id)
        ->  Seq Scan on interestingData c  (cost=0.00..628619.84 rows=28705684 width=20) (actual time=0.007..22862.174 rows=28705684 loops=1)
        ->  Hash  (cost=61661.57..61661.57 rows=83369 width=29) (actual time=67.638..67.638 rows=81101 loops=1)
              Buckets: 131072  Batches: 2  Memory Usage: 3565kB
              ->  Index Only Scan using dim1_id_dim3_idx on baseDimensions b  (cost=0.56..61661.57 rows=83369 width=29) (actual time=0.060..36.704 rows=81101 loops=1)
                    Index Cond: (dimension1 = '2016-08-20'::date)
                    Heap Fetches: 81101
Planning time: 0.265 ms
Execution time: 45218.174 ms

インデックス(dimension1、dimension3、id)も試しましたが、説明の結果はまったく同じです。

5
yankee

ネストされたループを介してこのクエリを解決するために必要なすべてのデータは、すでにRAMにキャッシュされているようです。 PostgreSQLのプランナーはそれを認識していません。多くのデータを取得するためにディスクに移動する必要があると想定しています。

すべてがキャッシュされている場合、それはかなりまたは不当に発生する可能性があります。

かなり多くのRAMがあり、ほとんどのデータがほとんどの場合キャッシュにある場合があります。その場合、random_page_costを同じまたはほぼ同じに下げるseq_page_costは正しい応答ですが、サーバーを再起動した場合、キャッシュがディスクからリロードされるため、ウォームアップが発生する可能性があることに注意してください。

別の「公平なキャッシュ」では、実際にまったく同じパラメーター(2016-08-20)を使用してまったく同じクエリを非常に頻繁に実行し、ほとんどのデータがそうである場合でも、特定のデータセットがメモリに残るようにします。ない。この場合、random_page_costを低くすると、この特定のクエリは修正できますが、他のクエリはさらに悪化します。 1つの解決策は、フレームワークで可能であれば、このクエリに対してのみrandom_page_costを下げるかenable_seqscan=offを設定し、後でリセットすることです。

「不当にキャッシュにある」とは、実際のクエリが毎回異なるパラメーターを使用するときに、2016-08-20のパラメーターでこのクエリをテストし続けることです。これは、パフォーマンステストサーバーが同じデータをディスクから読み取らずに何度も再利用できることを意味しますが、運用サーバーはそれを行うことができません。この場合、より現実的になるようにテスト/ベンチマーク方法を改善する必要があります。

3
jjanes