なぜですか?
理由 これは:
高速クエリ:
-> Hash Left Join (cost=1378.60..2467.48 rows=15 width=79) (actual time=41.759..85.037 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* (...)
遅いクエリ:
-> Hash Left Join (cost=1378.60..2467.48 rows=1 width=79) (actual time=35.084..80.209 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* unacc (...)
検索パターンを別の文字で拡張すると、Postgresはさらに少ないヒット数を想定します。 (通常、これは妥当な見積もりです。)Postgresには、実際に得られるのと同じ数のヒットを期待するのに十分な正確な統計がないことは明らかです(実際には、読み続けません)。
これにより、別のクエリプランに切り替わります。これは、実際のにはさらに最適ではありません。 ヒット数rows=1129
。
ソリューション
宣言されていないため、現在のPostgres9.5を想定しています。
状況を改善する1つの方法は、式インデックスを作成することです。 述語の式について。これにより、Postgresは実際の式の統計を収集します。これは、インデックス自体がクエリに使用されていない場合でも、クエリに役立ちます。 。インデックスがないと、統計がありません 表現のために。そして、正しく実行されれば、インデックスをクエリに使用でき、それはさらに優れています。しかし、複数の問題があります 現在の表現で:
unaccent(TEXT(coalesce(p.abrev、'')||'(' || coalesce(p.prenome、'')||')'))ilike unaccent('%vicen%' )
ストライク>
いくつかの仮定に基づいて、この更新されたクエリを検討してください 未公開のテーブル定義について:
SELECT e.id
, (SELECT count(*) FROM imgitem
WHERE tabid = e.id AND tab = 'esp') AS imgs -- count(*) is faster
, e.ano, e.mes, e.dia
, e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
, pl.pltag, e.inpa, e.det, d.ano anodet
, format('%s (%s)', p.abrev, p.prenome) AS determinador
, d.tax
, coalesce(v.val,v.valf) || ' ' || vu.unit AS altura
, coalesce(v1.val,v1.valf) || ' ' || vu1.unit AS dap
, d.fam, tf.nome família, d.gen, tg.nome AS gênero, d.sp
, ts.nome AS espécie, d.inf, e.loc, l.nome localidade, e.lat, e.lon
FROM pess p -- reorder!
JOIN det d ON d.detby = p.id -- INNER JOIN !
LEFT JOIN tax tf ON tf.oldfam = d.fam
LEFT JOIN tax tg ON tg.oldgen = d.gen
LEFT JOIN tax ts ON ts.oldsp = d.sp
LEFT JOIN tax ti ON ti.oldinf = d.inf -- unused, see @joop's comment
LEFT JOIN esp e ON e.det = d.id
LEFT JOIN loc l ON l.id = e.loc
LEFT JOIN var v ON v.esp = e.id AND v.key = 265
LEFT JOIN varunit vu ON vu.id = v.unit
LEFT JOIN var v1 ON v1.esp = e.id AND v1.key = 264
LEFT JOIN varunit vu1 ON vu1.id = v1.unit
LEFT JOIN pl ON pl.id = e.pl
WHERE f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%') OR
f_unaccent(p.prenome) ILIKE f_unaccent('%' || 'vicenti' || '%');
主なポイント
なぜf_unaccent()
? unaccent()
インデックスを作成できません。これを読んでください:
そこで概説されている関数を使用して、次の(推奨!)マルチカラム関数トリグラムGINインデックスを許可しました。 :
CREATE INDEX pess_unaccent_nome_trgm_idx ON pess
USING gin (f_unaccent(pess) gin_trgm_ops, f_unaccent(prenome) gin_trgm_ops);
トリグラムインデックスに慣れていない場合は、最初にこれを読んでください:
そしておそらく:
必ず最新バージョンのPostgres(現在は9.5)を実行してください。 GINインデックスが大幅に改善されました。そして、次のPostgres9.6でリリースされる予定のpg_trgm1.2の改善に興味があるでしょう:
準備されたステートメント パラメータを使用して(特にユーザー入力からのテキストを使用して)クエリを実行する一般的な方法です。 Postgresは、特定のパラメーターに最適なプランを見つける必要があります。 ワイルドカードを定数として追加 次のような検索語へ:
f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%')
('vicenti'
したがって、Postgresは、左にも右にも固定されていないパターンを処理していることを認識しています。これにより、さまざまな戦略が可能になります。詳細を含む関連回答:
または、検索語ごとにクエリを再計画することもできます(関数で動的SQLを使用する可能性があります)。ただし、計画の時間がパフォーマンスの向上を妨げていないことを確認してください。
WHERE
pess
の列の条件 。 Postgresはそれを LEFT JOIN
と矛盾します ストライク> INNERJOIN
に変換することを余儀なくされています 。さらに悪いことに、結合は結合ツリーの後半になります。また、Postgresは結合を並べ替えることができないため(以下を参照)、非常に高額になる可能性があります。テーブルを最初に移動します FROM
内の位置 行を早期に削除する句。次のLEFTJOIN
■定義上、行を削除しません。ただし、テーブルが非常に多いため、乗算される可能性のある結合を移動することが重要です。 最後まで行。
13個のテーブルを結合し、そのうち12個を LEFT JOIN
で結合します。 12!
が残ります 可能な組み合わせ-または11! * 2!
1つのLEFTJOIN
を取る場合 これは実際にはINNERJOIN
です。 。それも Postgresが最良のクエリプランのために可能なすべての順列を評価するための多く。 join_collapse_limit
について読む :
join_collapse_limit
のデフォルト設定 8です 、これは、Postgresが FROM
内のテーブルを並べ替えようとしないことを意味します 句とテーブルの順序は関連です 。
これを回避する1つの方法は、パフォーマンスが重要な部分を CTE
@joopコメント
。 join_collapse_limit
を設定しないでください 多くの結合テーブルを含むクエリプランニングの時間がはるかに長くなるか、時間が低下します。
連結日について 名前付きdata
:
cast(cast(e.ano as varchar(4))||'-' || right( '0' || cast(e.mes as varchar(2))、2)|| ' -'|| right(' 0'|| cast(e.dia as varchar(2))、2)as varchar(10))as data
ストライク>
想定 NOT NULL
で定義されている、年、月、日の3つの数値列から作成します。 、代わりにこれを使用してください:
e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
FM
について テンプレートパターン修飾子:
ただし、実際には、日付をデータ型dateとして保存する必要があります。
そもそも。
また簡略化:
format('%s (%s)', p.abrev, p.prenome) AS determinador
クエリが速くなることはありませんが、はるかにクリーンです。 format()を参照してください。コード>
。
最後に、パフォーマンスの最適化に関する通常のアドバイスをすべて行います。 適用:
これらすべてを正しく行うと、すべてに対してはるかに高速なクエリが表示されるはずです。 パターン。