次のような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を使用しています。
@ 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
を検討してください。この後の回答の詳細:
If列NULL
のmethod
値のパーセンテージが高い(実際の行サイズによっては〜20パーセントを超える)場合、 部分インデックス が役立つはずです:
CREATE INDEX methods_foo_idx ON methods (hash, string)
WHERE method IS NOT NULL;
(後の更新では、列がNOT NULL
であることを示しているため、該当しません。)
IfPostgreSQL9.2以降を実行している場合( @deszoがコメント )提示されたインデックスは、プランナがインデックスのみのスキャンを利用できる場合、CLUSTER
なしで役立つ場合があります。有利な条件でのみ適用可能:最後のVACUUM
およびクエリ内のすべての列がインデックスでカバーされているため、可視性マップに影響を与える書き込み操作はありません。基本的に読み取り専用のテーブルはこれをいつでも使用できますが、頻繁に書き込まれるテーブルは制限されています。 Postgres Wikiに詳細があります。
この場合、上記の部分インデックスはさらに便利です。
一方、の場合、列NULL
にnomethod
値があります。
1。)NOT NULL
を定義し、
2。)count(*)
の代わりにcount(method)
を使用します。これは少し高速で、NULL
値がない場合も同じです。
Ifこのクエリを頻繁に呼び出す必要があり、テーブルが読み取り専用の場合、 MATERIALIZED VIEW
を作成します 。
エキゾチックな細かい点:テーブルの名前はnostring
ですが、ハッシュが含まれているようです。文字列の代わりにハッシュを除外することにより、意図したより多くの文字列を除外する可能性があります。 極端にありそうもないが、可能である。
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
の出力を確認することをお勧めします。)
ハッシュはmd5なので、おそらく数値に変換しようとするかもしれません。数値として保存するか、不変関数でその数値を計算する関数インデックスを作成するだけです。
他の人々はすでに、md5値(の一部)をテキストから文字列に変換するpl/pgsql関数を作成しています。例については https://stackoverflow.com/questions/9809381/hashing-a-string-to-a-numeric-value-in-postgressql を参照してください
インデックスのスキャン中は、文字列の比較に多くの時間を費やしていると思います。その値を数値として保存できれば、本当に高速になるはずです。
私はこの問題に何度も遭遇し、単純な2つの部分からなるトリックを発見しました。
ハッシュ値に部分文字列インデックスを作成します:(7は通常適切な長さです)
create index methods_idx_hash_substring ON methods(substring(hash,1,7))
検索/結合に部分文字列の一致が含まれているため、クエリプランナーはインデックスを使用するように示唆されています。
古い:_WHERE hash = :kwarg
_
新規:WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))
Raw hash
のインデックスも必要です。
その結果(通常)、プランナーは最初にサブストリングインデックスを調べ、ほとんどの行を除外します。次に、32文字のハッシュ全体を対応するインデックス(またはテーブル)と照合します。このアプローチでは、800ミリ秒のクエリが4つに減少しました。