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


14

次のような、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列はのMD5ハッシュであるstringとインデックスを持っています。私の問題は、テーブル全体がハッシュではなくIDでソートされているため、最初にソートしてからグループ化するのに時間がかかることだと思いますか?

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

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


1
使用するPostgreSQL のバージョンを常に提供してください。NULL列の値の割合は何methodですか?に重複はありstringますか?
アーウィンブランドステッター

回答:


18

LEFT JOIN@ dezsoの答えは、良いことがあります。ただし、クエリはとにかくテーブル全体を読み取る必要があるため、インデックスはほとんど役に立ちません(例外は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は、どちらが排他ロックなしで同じことができるかを検討してください。この後の回答の詳細:


場合のパーセンテージNULL列の値は、method(実際の行サイズに応じて、〜20%以上)が高い、部分インデックスを助けるべきです。

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

(後の更新では、列がであることが示されているNOT NULLため、適用されません。)

PostgreSQL 9.2以降を実行している場合@deszoのコメント付き)、提示されたインデックスはCLUSTER、プランナーがインデックスのみのスキャンを利用できる場合に役立ちます。有利な条件下でのみ適用可能:VACUUMクエリの最後の列とすべての列をインデックスでカバーする必要があるため、可視性マップに影響する書き込み操作はありません。基本的に読み取り専用テーブルはいつでもこれを使用できますが、大量に書き込まれるテーブルは制限されています。詳細については、Postgres Wikiをご覧ください。

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

場合は、他の一方で、存在しない何の NULL列の値はmethod、あなたがすべきである
1)、それを定義NOT NULLし、
2)使用count(*)の代わりに、count(method)若干高速だとの不存在下で同じことを行い、NULL値を。

場合あなたは、多くの場合、このクエリを呼び出す必要があり、テーブルは読み取り専用で、作成しますMATERIALIZED VIEW


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


クラスターの方がはるかに高速です。クエリにはまだ約5分必要ですが、一晩中実行するよりもはるかに優れています:D
reox

@reox:v9.2を実行しているので、クラスタリングの前にインデックスのみでテストしましたか?違いを見たら面白いでしょう。(クラスタリング後に違いを再現することはできません。)また(これは安価です)、EXPLAINはインデックススキャンまたは全テーブルスキャンを表示しますか?
アーウィンブランドステッター

5

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コスト自体は操作にかかった時間を示さないため、の出力を確認する方が良いでしょう。)


インデックスはnostring.hashにすでに作成されていますが、タプルが多すぎるためpostgresは使用しないと思います...シーケンススキャンを無効にすると、インデックスが使用されます。私は左を使用している場合...私はより良い、その方法ので、3200万のコストを取得...しかし、私はより多くのそれを最適化しようとしている参加
reox

3
コストは、プランナーが十分に優れたプランを選択できるようにするためのものです。通常、実際の時間はそれと相関しますが、必ずしもそうではありません。確認したい場合は、を使用してくださいEXPLAIN ANALYZE
-dezso

1

ハッシュはmd5であるため、おそらく数値に変換しようとする可能性があります。数値として保存するか、不変関数でその数値を計算する関数インデックスを作成するだけです。

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

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


1
私はこの変換が物事をスピードアップすることを疑います。ここでのすべてのクエリは、比較のために平等を使用しています。数値表現を計算してから平等をチェックすることは、私にとって大きな利益を約束しません。
-dezso

2
スペース効率のために、数字ではなくbyteaとしてmd5を保存すると思います:sqlfiddle.com/#!
Jackはtry tryanswers.xyz


@JackDouglas:興味深いコメント!大きなテーブルでは、md5ごとに32バイトではなく16バイトがかなり大きいです。
アーウィンブランドステッター

0

私はこの問題に頻繁に遭遇し、簡単な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つに減りました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.