sql >> データベース >  >> RDS >> PostgreSQL

検索語を少し変更すると、クエリが大幅に遅くなるのはなぜですか?

    なぜですか?

    理由 これは:

    高速クエリ:

    ->  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の列の条件 LEFT JOINと矛盾します 。 Postgresはそれを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' || cas​​t(e.mes as varchar(2))、2)|| ' -'|| right(' 0'|| cas​​t(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()

    最後に、パフォーマンスの最適化に関する通常のアドバイスをすべて行います。 適用:

    これらすべてを正しく行うと、すべてに対してはるかに高速なクエリが表示されるはずです。 パターン。



    1. OracleSQLクエリのログ

    2. MacOSX10.6上のPythonmysqldbが機能しない

    3. BigQuery / SQLで大量のデータを含む列に行を転置するにはどうすればよいですか?

    4. pglogicalレプリケートテーブルのトリガーから通知