PostgreSQL LIKEクエリのパフォーマンスの変化


112

LIKEデータベース内の特定のテーブルに対するクエリに関して、応答時間にかなり大きな変動が見られます。200〜400ミリ秒以内に結果が得られる場合がありますが(非常に許容範囲内)、結果が返されるまでに30秒ほどかかる場合もあります。

LIKEクエリは非常に多くのリソースを必要とすることを理解していますが、応答時間にこのような大きな違いが生じる理由を理解していません。owner1フィールドにbtreeインデックスを作成しましたが、LIKEクエリに役立つとは思いません。誰かアイデアはありますか?

サンプルSQL:

SELECT gid, owner1 FORM parcels
WHERE owner1 ILIKE '%someones name%' LIMIT 10

私も試しました:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%') LIMIT 10

そして:

SELECT gid, owner1 FROM parcels
WHERE lower(owner1) LIKE lower('someones name%') LIMIT 10

同様の結果。
テーブルの行数:約95,000。

回答:


280

FTSはサポートしていません LIKE

以前に受け入れられた答えは間違っていました。フルテキストインデックスを使用したフルテキスト検索は、演算子にはまったく対応してLIKEいません。独自の演算子があり、任意の文字列に対しては機能しません。辞書とステミングに基づく単語で動作します。それはありませんサポート単語の接頭辞マッチングをしてではなく、LIKEオペレータ:

トライグラムインデックス LIKE

GINおよびGiSTトライグラムインデックスのpg_trgm演算子クラスを提供する追加モジュールをインストールして、左アンカーされたものだけでなく、すべてパターンをサポートします。LIKEILIKE

インデックスの例:

CREATE INDEX tbl_col_gin_trgm_idx  ON tbl USING gin  (col gin_trgm_ops);

または:

CREATE INDEX tbl_col_gist_trgm_idx ON tbl USING gist (col gist_trgm_ops);

クエリの例:

SELECT * FROM tbl WHERE col LIKE '%foo%';   -- leading wildcard
SELECT * FROM tbl WHERE col ILIKE '%foo%';  -- works case insensitively as well

トライグラム?短い文字列はどうですか?

インデックス付きの値が3文字未満の単語でも機能します。マニュアル:

文字列に含まれるトライグラムのセットを決定するとき、各単語には2つのスペースが前に付き、1つのスペースが後ろにあると見なされます。

3文字未満のパターンを検索しますか?マニュアル:

LIKE正規表現検索と正規表現検索の両方で、抽出可能なトライグラムのないパターンはフルインデックススキャンに退化することに注意してください。

つまり、そのインデックス/ビットマップインデックススキャンは引き続き機能し(準備されたステートメントのクエリプランは機能しません)、パフォーマンスが向上するわけではありません。通常、大きな損失はありません。1文字または2文字の文字列はほとんど選択できず(基になるテーブルの一致の数パーセント以上)、インデックスのサポートは、全テーブルスキャンの方が速いため、最初はパフォーマンスを向上させません。


text_pattern_ops プレフィックス照合

ちょうどのために左アンカーパターン(無大手ワイルドカード)あなたは、適切なと最適な取得演算子クラス B-Treeインデックスのために:text_pattern_opsvarchar_pattern_ops。標準Postgresの両方の組み込み機能。追加のモジュールは必要ありません。パフォーマンスは同じですが、インデックスははるかに小さくなります。

インデックスの例:

CREATE INDEX tbl_col_text_pattern_ops_idx ON tbl(col text_pattern_ops);

クエリの例:

SELECT * FROM tbl WHERE col LIKE 'foo%';  -- no leading wildcard

または、データベースを'C'ロケール(事実上ロケールなし)で実行する必要がある場合は、すべてがいずれにしてもバイト順に従ってソートされ、デフォルトの演算子クラスを持つプレーンなbtreeインデックスが機能します。

dba.SEに関するこれらの関連回答の詳細、説明、例、リンク:


500K行のテーブルに先行するワイルドカードがないため、gin_trgm_opsを使用したginインデックスは、btreeよりも10倍高速に表示されます
nicolas

@nicolas:比較は多くの変数に依存します。キーの長さ、データの分布、パターンの長さ、可能なインデックスのみスキャン...そして最も重要なこと:Postgresバージョン。GINインデックスは、ページ9.4および9.5で大幅に改善されました。pg_trgmの新しいバージョン(pg 9.6でリリース予定)は、さらに改善をもたらす予定です。
Erwin Brandstetter 2016年

1
私がドキュメントを正しく取得した場合pg_trgm、たとえば、長さが3文字以上のクエリ文字列が必要な場合、たとえば、fo%インデックスにヒットせず、代わりにスキャンを実行します。注意すべき点。
Tuukka Mustonen 2017年

1
@TuukkaMustonen:良い点。(ビットマップ)インデックススキャンは引き続き機能しますが、パフォーマンスは向上しません。上記の説明をいくつか追加しました。
Erwin Brandstetter 2017年

7

おそらく、高速なものは、インデックスを使用できるように、大文字と小文字を区別するアンカーパターンです。つまり、一致文字列の先頭にワイルドカードがないため、エグゼキュータはインデックス範囲スキャンを使用できます。(ドキュメント内の関連するコメントはここにあります)Lowerとilikeは、その目的のために特別にインデックスを作成しない限り、インデックスを使用する機能も失います(機能インデックスを参照)。

フィールドの中央で文字列を検索する場合は、フルテキストインデックスまたはトライグラムインデックスを調べる必要があります。1つはPostgresコアにあり、もう1つはcontribモジュールで使用できます。


フィールドの小文字の値にインデックスを作成することは考えていませんでした。そうすれば、クエリを実行する前に、クエリテキストをバックエンドで小文字に変換できます。
Jason

4

PostgreSQLには別のタイプのインデックスであるWildspeedをインストールできます。Wildspeedは%word%のワイルドカードで機能します。問題ありません。欠点はインデックスのサイズです。これは非常に大きくなる可能性があります。


3

postgresqlでのLIKEクエリのパフォーマンスを向上させるには、以下のクエリを実行してください。より大きなテーブルには、次のようなインデックスを作成します。

CREATE INDEX <indexname> ON <tablename> USING btree (<fieldname> text_pattern_ops)

これは、パターンがワイルドカードで始まらない場合にのみ機能します。この場合、最初の2つのサンプルクエリはどちらもワイルドカードで始まります。
cbz

1

Django ORMは、それだけの価値があるUPPER(text)ため、LIKE、それは大文字と小文字を区別するために、クエリ

インデックスを追加するUPPER(column::text)と、他のものとは異なり、システムが大幅に高速化されました。

先頭の%までは、はい、インデックスは使用されません。すばらしい説明はこのブログを見てください:

https://use-the-index-luke.com/sql/where-clause/searching-for-ranges/like-performance-tuning


1

最近、200000レコードを含むテーブルで同様の問題があり、LIKEクエリを繰り返し実行する必要があります。私の場合、検索されている文字列は修正されました。他の分野は様々でした。そのため、私は書き直すことができました:

SELECT owner1 FROM parcels
WHERE lower(owner1) LIKE lower('%someones name%');

なので

CREATE INDEX ix_parcels ON parcels(position(lower('someones name') in lower(owner1)));

SELECT owner1 FROM parcels
WHERE position(lower('someones name') in lower(owner1)) > 0;

クエリが速く戻ってきて、インデックスが以下で使用されていることを確認したときは嬉しかったですEXPLAIN ANALYZE

 Bitmap Heap Scan on parcels  (cost=7.66..25.59 rows=453 width=32) (actual time=0.006..0.006 rows=0 loops=1)
   Recheck Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
   ->  Bitmap Index Scan on ix_parcels  (cost=0.00..7.55 rows=453 width=0) (actual time=0.004..0.004 rows=0 loops=1)
         Index Cond: ("position"(lower(owner1), 'someones name'::text) > 0)
 Planning time: 0.075 ms
 Execution time: 0.025 ms

0

Likeクエリは、作成したインデックスを使用できない可能性があります。

1)LIKE基準はワイルドカードで始まります。

2)LIKE基準で関数を使用しました。


0

LIKE、ILIKE、upper、lowerなどの関数を含む列で句を使用する場合、postgresは通常のインデックスを考慮しません。各行を通過するテーブルのフルスキャンを実行するため、処理が遅くなります。

正しい方法は、クエリに従って新しいインデックスを作成することです。たとえば、大文字と小文字を区別せずに列を一致させたい場合、私の列はvarcharです。その後、このようにできます。

create index ix_tblname_col_upper on tblname (UPPER(col) varchar_pattern_ops);

同様に、列がテキストの場合は、次のようにします

create index ix_tblname_col_upper on tblname (UPPER(col) text_pattern_ops);

同様に、上部の機能を必要な他の機能に変更できます。

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