20Mを超えるタプルを持つPostgresテーブルが1つあります。
first_name | last_name | email
-------------------------------------------
bat | man | [email protected]
arya | vidal | [email protected]
max | joe | [email protected]
使用しているレコードをフィルタリングするには:
SELECT *
FROM people
WHERE (first_name || '' || last_name) ILIKE '%bat%man%' OR
first_name ILIKE '%bat%man%' OR
last_name ILIKE '%bat%man%' OR
email ILIKE '%bat%man%'
LIMIT 25 OFFSET 0
インデックスを使用した場合でも、結果が返されるまでに検索はほぼ1分かかります。(first_name || '' || last_name)
、first_name
、last_name
、email
のインデックスがあります。
このクエリのパフォーマンスを向上させるにはどうすればよいですか?
パターンマッチングの種類には、トライグラムインデックスを使用するのが最適です。 最初にお読みください:
式_(first_name || '' || last_name)
_にタイプミスがあると想定します。これは空の文字列では意味がなく、スペース文字を含む_(first_name || ' ' || last_name)
_が本当に必要です。
どちらかの列がNULLになる可能性があると仮定すると、NULLセーフな連結が必要になります。簡単な解決策はconcat_ws()
です。
ただし、その関数はIMMUTABLE
(リンクされた回答の説明)ではないため、インデックス式で直接使用することはできません。 IMMUTABLE
関数ラッパーを使用できます。
_CREATE OR REPLACE FUNCTION f_immutable_concat_ws(s text, t1 text, t2 text)
RETURNS text AS
$func$
SELECT concat_ws(s, t1, t2)
$func$ LANGUAGE sql IMMUTABLE;
_
ラッパーはIMMUTABLE
パラメーターのみを取るため、text
にすることができます。
どちらにしても、これはより冗長ですが、内部オーバーヘッドが少なく、かなり高速です。
_CREATE OR REPLACE FUNCTION f_immutable_concat_ws1(s text, t1 text, t2 text)
RETURNS text AS
$func$
SELECT CASE
WHEN t1 IS NULL THEN t2
WHEN t2 IS NULL THEN t1
ELSE t1 || s || t2
END
$func$ LANGUAGE sql IMMUTABLE;
_
または、ハードコードされたスペース文字を使用します。
_CREATE OR REPLACE FUNCTION f_concat_space(t1 text, t2 text)
RETURNS text AS
$func$
SELECT CASE
WHEN t1 IS NULL THEN t2
WHEN t2 IS NULL THEN t1
ELSE t1 || ' ' || t2
END
$func$ LANGUAGE sql IMMUTABLE;
_
この機能に基づいて索引を作成することをお勧めします。
_CREATE INDEX people_special_gin_trgm_idx ON people
USING gin (f_concat_space(first_name, last_name) gin_trgm_ops, email gin_trgm_ops);
_
いくつかの考慮事項のために、2番目のインデックス列としてemail
を追加しました。
インデックスの作成には、2,000万行の処理に時間がかかりますが、トップロード時ではなく、_CREATE INDEX CONCURRENTLY ...
_を使用するのが最適です。 GINインデックスは、プレーンなbtreeインデックスよりもかなり大きく、維持にコストがかかります。必ず最新バージョンのPostgresを実行してください。最近のバージョンでは、GINインデックスに大きな改良が加えられています。
次に、少し調整して簡略化したクエリを高速かつ正確にする必要があります:
_SELECT *
FROM people
WHERE f_concat_space(first_name, last_name) ILIKE '%bat%man%' OR
email ILIKE '%bat%man%'
LIMIT 25;
_
このクエリには、oneインデックスのみが必要です。
パターンマッチングの基本: