web-dev-qa-db-ja.com

GROUP BYおよびORDER BYを使用した大きなテーブルでのクエリが遅い

次のような720万タプルのテーブルがあります。

                               table public.methods
 column |          type         |                      attributes
--------+-----------------------+----------------------------------------------------
 id     | integer               | not null DEFAULT nextval('methodkey'::regclass)
 hash   | character varying(32) | not null
 string | character varying     | not null
 method | character varying     | not null
 file   | character varying     | not null
 type   | character varying     | not null
Indexes:
    "methods_pkey" PRIMARY KEY, btree (id)
    "methodhash" btree (hash)

次に、いくつかの値を選択したいのですが、クエリが非常に遅くなっています。

db=# explain 
    select hash, string, count(method) 
    from methods 
    where hash not in 
          (select hash from nostring) 
    group by hash, string 
    order by count(method) desc;
                                            QUERY PLAN
----------------------------------------------------------------------------------------
 Sort  (cost=160245190041.10..160245190962.07 rows=368391 width=182)
   Sort Key: (count(methods.method))
   ->  GroupAggregate  (cost=160245017241.77..160245057764.73 rows=368391 width=182)
       ->  Sort  (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
             Sort Key: methods.hash, methods.string
             ->  Seq Scan on methods  (cost=0.00..160243305942.27 rows=3683905 width=182)
                   Filter: (NOT (SubPlan 1))
                   SubPlan 1
                   ->  Materialize  (cost=0.00..41071.54 rows=970636 width=33)
                     ->  Seq Scan on nostring  (cost=0.00..28634.36 rows=970636 width=33)

hash列はstringのmd5ハッシュであり、インデックスがあります。私の問題は、テーブル全体がハッシュではなくIDで並べ替えられているため、最初に並べ替えてからグループ化するのに時間がかかるのではないかと思います。

テーブルnostringには、不要なハッシュのリストのみが含まれています。しかし、両方のテーブルにすべての値が必要です。したがって、これらを削除することはできません。

追加情報:どの列もnullにすることはできず(テーブル定義で修正されました)、私はpostgresql 9.2を使用しています。

14
reox

@ dezsoの答えLEFT JOINは良いはずです。ただし、クエリはとにかくテーブル全体を読み取る必要があるため、インデックス自体はほとんど役に立ちません(例外はPostgres 9.2以降でのインデックスのみのスキャンと好ましい条件です。以下を参照)。

SELECT m.hash, m.string, count(m.method) AS method_ct
FROM   methods m
LEFT   JOIN nostring n USING (hash)
WHERE  n.hash IS NULL
GROUP  BY m.hash, m.string 
ORDER  BY count(m.method) DESC;

クエリでEXPLAIN ANALYZEを実行します。キャッシュ効果とノイズを除外するために数回。最良の結果を比較します。

クエリに一致する複数列のインデックスを作成します。

CREATE INDEX methods_cluster_idx ON methods (hash, string, method);

待つ?インデックスが役に立たないと言った後?まあ、それはテーブルをCLUSTERするために必要です:

CLUSTER methods USING methods_cluster_idx;
ANALYZE methods;

EXPLAIN ANALYZEを再実行します。もっと速く?そのはず。

CLUSTERは、使用されたインデックスの順序でテーブル全体を書き換える1回限りの操作です。また、実際にはVACUUM FULLでもあります。確認したい場合は、VACUUM FULLだけを使用して事前テストを実行し、その原因が何であるかを確認します。

テーブルで多数の書き込み操作が検出されると、時間の経過とともに効果が低下します。効果を回復するには、CLUSTERを営業時間外にスケジュールします。微調整は、正確なユースケースによって異なります。 CLUSTERに関するマニュアル

CLUSTERはかなり粗雑なツールであり、テーブルの排他ロックが必要です。余裕がない場合は、排他ロックなしでも同じことができる pg_repack を検討してください。この後の回答の詳細:


IfNULLmethod値のパーセンテージが高い(実際の行サイズによっては〜20パーセントを超える)場合、 部分インデックス が役立つはずです:

CREATE INDEX methods_foo_idx ON methods (hash, string)
WHERE method IS NOT NULL;

(後の更新では、列がNOT NULLであることを示しているため、該当しません。)

IfPostgreSQL9.2以降を実行している場合( @deszoがコメント )提示されたインデックスは、プランナがインデックスのみのスキャンを利用できる場合、CLUSTERなしで役立つ場合があります。有利な条件でのみ適用可能:最後のVACUUMおよびクエリ内のすべての列がインデックスでカバーされているため、可視性マップに影響を与える書き込み操作はありません。基本的に読み取り専用のテーブルはこれをいつでも使用できますが、頻繁に書き込まれるテーブルは制限されています。 Postgres Wikiに詳細があります。

この場合、上記の部分インデックスはさらに便利です。

一方、の場合、列NULLnomethod値があります。
1。)NOT NULLを定義し、
2。)count(*)の代わりにcount(method)を使用します。これは少し高速で、NULL値がない場合も同じです。

Ifこのクエリを頻繁に呼び出す必要があり、テーブルが読み取り専用の場合、 MATERIALIZED VIEWを作成します


エキゾチックな細かい点:テーブルの名前はnostringですが、ハッシュが含まれているようです。文字列の代わりにハッシュを除外することにより、意図したより多くの文字列を除外する可能性があります。 極端にありそうもないが、可能である。

18

DBA.SEへようこそ!

次のようにクエリを言い換えることができます:

SELECT m.hash, string, count(method) 
FROM 
    methods m
    LEFT JOIN nostring n ON m.hash = n.hash
WHERE n.hash IS NULL
GROUP BY hash, string 
ORDER BY count(method) DESC;

または別の可能性:

SELECT m.hash, string, count(method) 
FROM 
    methods m
WHERE NOT EXISTS (SELECT hash FROM nostring WHERE hash = m.hash)
GROUP BY hash, string 
ORDER BY count(method) DESC;

NOT INは、インデックスを使用するのが難しいため、パフォーマンスの典型的なシンクです。

これは、インデックスでさらに強化できます。 nostring.hashのインデックスは便利に見えます。しかし、最初に:今何を手に入れていますか? (コスト自体は操作にかかった時間を示していないため、EXPLAIN ANALYZEの出力を確認することをお勧めします。)

5
dezso

ハッシュはmd5なので、おそらく数値に変換しようとするかもしれません。数値として保存するか、不変関数でその数値を計算する関数インデックスを作成するだけです。

他の人々はすでに、md5値(の一部)をテキストから文字列に変換するpl/pgsql関数を作成しています。例については https://stackoverflow.com/questions/9809381/hashing-a-string-to-a-numeric-value-in-postgressql を参照してください

インデックスのスキャン中は、文字列の比較に多くの時間を費やしていると思います。その値を数値として保存できれば、本当に高速になるはずです。

1
eppesuig

私はこの問題に何度も遭遇し、単純な2つの部分からなるトリックを発見しました。

  1. ハッシュ値に部分文字列インデックスを作成します:(7は通常適切な長さです)

    create index methods_idx_hash_substring ON methods(substring(hash,1,7))

  2. 検索/結合に部分文字列の一致が含まれているため、クエリプランナーはインデックスを使用するように示唆されています。

    古い:_WHERE hash = :kwarg_

    新規:WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))

Raw hashのインデックスも必要です。

その結果(通常)、プランナーは最初にサブストリングインデックスを調べ、ほとんどの行を除外します。次に、32文字のハッシュ全体を対応するインデックス(またはテーブル)と照合します。このアプローチでは、800ミリ秒のクエリが4つに減少しました。

0