次の形式の高価で定期的なPostgreSQL 9.3クエリがあります。
SELECT * from mytable where name NOT LIKE '%foo%';
foo
は、実際には変更されない定数です。クエリはテーブルスキャンを必要とするため、コストがかかります。クエリに実際に一致するのは数行のみであるため、部分インデックスを使用してクエリを高速化したいと考えています。
CREATE INDEX foo_idx ON mytable ((name NOT LIKE '%foo%')) WHERE name NOT LIKE '%foo%';
ただし、クエリでEXPLAIN
を実行すると、新しいインデックスに依存するのではなく、引き続き順次スキャンが使用されます。 何が悪いのですか?
CREATE INDEX
ステートメントの括弧で囲まれた式の句の中でいくつかの異なる式を試しましたが、何も役に立ちません:
CREATE INDEX foo_idx ON mytable (name) WHERE name NOT LIKE '%foo%';
CREATE INDEX foo_idx ON mytable ((name LIKE '%foo%')) WHERE name NOT LIKE '%foo%';
また、インデックスのWHERE
句を削除しようとしましたが、それも役に立ちません。
インデックスはそのまま機能します。 @ jjanesで指摘 のように、ANALYZE
を実行するだけで済みます。ただし、インデックスを変更することをお勧めします。定義内の式は役に立ちません。
CREATE INDEX foo_idx ON intentions ((data LIKE '%foo%')) WHERE data NOT LIKE '%foo%';
式は常にFALSE
であり、役に立たないノイズです。少し安い同等のインデックスに単純化します。
CREATE INDEX foo_idx ON intentions ((FALSE)) WHERE data NOT LIKE '%foo%';
または、さらに便利なことに、何か便利なものを入れてください。とにかく、各インデックスエントリはデータにMAXALIGN
(通常8バイト)を割り当て、index行あたりのオーバーヘッドはさらに12です。バイト(およびbtreeインデックスのデフォルトのfillfactor
は90%です)。インデックスを有効に使用して、他のクエリを支援することもできます。同じ部分インデックスを使用する可能性のある他のクエリがある場合は?
最大8バイトの価値のあるもの(1つまたは2つのint4
列など)を配置すると、インデックスはまったく同じサイズとパフォーマンスを保持しますが、より多くの可能なユースケースを提供します。列(の1つ)が更新に関係している場合、追加の保守コストがかかります。それでも、何か役に立つものがあれば、それを使用してください。 JOIN
/WHERE
またはORDER BY
句で使用される追加の列。または、インデックスのみのスキャンを有効にする列:
CREATE INDEX foo_idx ON intentions (<useful_column(s)>)
WHERE data NOT LIKE '%foo%';
少し異なるアプローチを試すことができます。
CREATE INDEX foo_idx ON mytable (strpos(name,'foo'));
SELECT * from mytable where strpos(name,'foo') = 0;
NOT LIKE
に問題があります。 NOT
なしでこのクエリを想像してください。インデックスをウォークして、一致する値の間隔(インデックスはソートされたリスト)を見つけることができます。 NOT
は、一致する間隔の前後の両方で、他のすべてが必要であることを意味します。それはそれを行うかなり特別なオプティマイザです。
doに「foo」が含まれるすべての行を特定するためにインデックスを反転させ、その結果をNOT EXISTS
(または同等のもの)を使用して、目的の出力を見つけます。ただし、テーブルスキャンは引き続き表示されます。
または、列IsFoo
をテーブルに追加します。これをテーブル書き込みに入力します(おそらく、永続的な計算列を使用しますか?)。インデックスとフィルターにIsFoo
を含めます。
適切なクエリプランを有効にするには、ANALYZE
を実行する必要がありました。これが行われると、すべてが魅力のように機能しました。私の最終的なインデックスは次のようになります:
CREATE INDEX foo_idx ON intentions ((data LIKE '%foo%')) WHERE data NOT LIKE '%foo%';