web-dev-qa-db-ja.com

ILIKEパターンのトライグラムインデックスが期待どおりに機能しない

私は単純ですが遅いクエリを持っています:

SELECT DISTINCT title  
FROM ja_jobs
WHERE title ILIKE '%RYAN WER%'
AND clientid = 31239
AND time_job > 1457826264
ORDER BY title
LIMIT 10;

分析の説明:

Limit  (cost=5946.40..5946.41 rows=1 width=19) (actual time=2746.759..2746.772 rows=1 loops=1)
  ->  Unique  (cost=5946.40..5946.41 rows=1 width=19) (actual time=2746.753..2746.763 rows=1 loops=1)
        ->  Sort  (cost=5946.40..5946.41 rows=1 width=19) (actual time=2746.750..2746.754 rows=4 loops=1)
              Sort Key: "title"
              Sort Method: quicksort  Memory: 25kB
              ->  Bitmap Heap Scan on "ja_jobs"  (cost=49.02..5946.39 rows=1 width=19) (actual time=576.275..2746.609 rows=4 loops=1)
                    Recheck Cond: (("clientid" = 31239) AND ("time_job" > 1457826264))
                    Filter: (("title")::"text" ~~* '%RYAN WER%'::"text")
                    Rows Removed by Filter: 791
                    ->  Bitmap Index Scan on "ix_jobs_client_times"  (cost=0.00..49.02 rows=1546 width=0) (actual time=100.870..100.870 rows=795 loops=1)
                          Index Cond: (("clientid" = 31239) AND ("time_job" > 1457826264))
Total runtime: 2746.879 ms

次に、トライグラムインデックスを作成しました。

CREATE INDEX ix_ja_jobs_trgm_gin ON public.ja_jobs USING gin (title gin_trgm_ops);

インデックス追加後の分析の説明:(はい、私はanalyze

Limit  (cost=389.91..389.91 rows=1 width=20) (actual time=3720.511..3720.511 rows=0 loops=1)
  ->  Unique  (cost=389.91..389.91 rows=1 width=20) (actual time=3720.507..3720.507 rows=0 loops=1)
        ->  Sort  (cost=389.91..389.91 rows=1 width=20) (actual time=3720.505..3720.505 rows=0 loops=1)
              Sort Key: "title"
              Sort Method: quicksort  Memory: 25kB
              ->  Bitmap Heap Scan on "ja_jobs"  (cost=385.88..389.90 rows=1 width=20) (actual time=3720.497..3720.497 rows=0 loops=1)
                    Recheck Cond: (("clientid" = 31239) AND ("time_job" > 1457826264) AND (("title")::"text" ~~ '%RYAN WER%'::"text"))
                    Rows Removed by Index Recheck: 4
                    ->  BitmapAnd  (cost=385.88..385.88 rows=1 width=0) (actual time=3720.469..3720.469 rows=0 loops=1)
                          ->  Bitmap Index Scan on "ix_jobs_client_times"  (cost=0.00..50.00 rows=1644 width=0) (actual time=0.142..0.142 rows=795 loops=1)
                                Index Cond: (("clientid" = 31239) AND ("time_job" > 1457826264))
                          ->  Bitmap Index Scan on "ix_ja_jobs_trgm_gin"  (cost=0.00..335.63 rows=484 width=0) (actual time=3720.213..3720.213 rows=32 loops=1)
                                Index Cond: (("title")::"text" ~~ '%RYAN WER%'::"text")
Total runtime: 3720.653 ms

ご覧のとおり、インデックスは機能しませんでした。

テーブルpublic.ja_jobs

CREATE TABLE public.ja_jobs (
  id bigint NOT NULL DEFAULT "nextval"('"ja_jobs_id_seq"'::"regclass"),
  refnum character varying(100) NOT NULL DEFAULT ''::character varying,
  clientid bigint NOT NULL DEFAULT 0,
  customerid bigint,
  time_job bigint,
  priority smallint NOT NULL DEFAULT 0,
  status character varying(255) NOT NULL DEFAULT 'active'::"bpchar",
  title character varying(100) NOT NULL DEFAULT ''::character varying,

  -- some other irrelevant columns
)

public.ja_jobsのインデックス:

Indexes:
    "ja_jobs_pkey" PRIMARY KEY, "btree" ("id")
    "ix_bill_customer_jobs" "btree" ("customerid", "bill_customer")
    "ix_clientid_jobs" "btree" ("clientid")
    "ix_customerid_job" "btree" ("customerid")
    "ix_ja_jobs_clientid_modified_date_created_date" "btree" ("clientid", "modified_date", "created_date")
    "ix_ja_jobs_gsdi_pk" "btree" (("id"::"text"))
    "ix_ja_jobs_trgm_gin" "gin" ("title" "gin_trgm_ops")
    "ix_job_customer_recent_jobs_lookaside_bill_customer" "btree" ("bill_customer", "modified_date")
    "ix_job_customer_recent_jobs_lookaside_clientid" "btree" ("clientid", "modified_date")
    "ix_job_customer_recent_jobs_lookaside_customer" "btree" ("customerid", "modified_date")
    "ix_jobs_charges_and_parts_sort" "btree" (("charges_count" + "parts_count"))
    "ix_jobs_client_times" "btree" ("clientid", "time_job", "time_arrival")
    "ix_jobs_fts_description_en" "gin" ("full_text_universal_cast"("description"))
    "ix_jobs_fts_full_address_en" "gin" ((((("full_text_universal_cast"("address"::"text") || "full_text_universal_cast"("suburb"::"text")) || "full_text_universal_cast"("city"::"text")) || "full_text_universal_cast"("stpr"::"text")) || "full_text_universal_cast"("postc
ode"::"text")))
    "ix_jobs_fts_job_number_en" "gin" ("full_text_universal_cast"("job_number"::"text"))
    "ix_jobs_fts_refnum_en" "gin" ("full_text_universal_cast"("refnum"::"text"))
    "ix_jobs_fts_title_en" "gin" ("full_text_universal_cast"("title"::"text"))
    "ix_jobs_full_address_street_first" "btree" (((((COALESCE("address"::character varying, ''::character varying)::"text" || COALESCE(' '::"text" || "suburb"::"text", ''::"text")) || COALESCE(' '::"text" || "city"::"text", ''::"text")) || COALESCE(' '::"text" || "postc
ode"::"text", ''::"text")) || COALESCE(' '::"text" || "stpr"::"text", ''::"text")))
    "ix_jobs_paying_customers" "btree" ((COALESCE("bill_customer", "customerid")))
    "ix_jobs_status_label_ids" "btree" ("status_label_id")
    "ix_jobs_top_by_client" "btree" ("id", "clientid")
    "ix_mobiuser_jobs" "btree" ("accepted_mobile_user")
    "ix_recurrenceid_jobs" "btree" ("recurrenceid")
    "ix_timejob_jobs" "btree" ("time_job")
    "ja_jobs_client_type" "btree" ("clientid", "jobtype")
    "ja_jobs_the_geom_idx" "Gist" ("the_geom")

質問:

クエリを改善するにはどうすればよいですか?トライグラムインデックスが期待どおりに機能しないのはなぜですか?

PDATE: Explain分析バッファーを再実行しました

Limit  (cost=199669.37..199669.39 rows=10 width=20) (actual time=31523.690..31523.691 rows=1 loops=1)
  Buffers: shared hit=26947 read=101574 dirtied=438
  ->  Sort  (cost=199669.37..199669.40 rows=11 width=20) (actual time=31523.686..31523.686 rows=1 loops=1)
        Sort Key: "title"
        Sort Method: quicksort  Memory: 25kB
        Buffers: shared hit=26947 read=101574 dirtied=438
        ->  Bitmap Heap Scan on "ja_jobs"  (cost=4850.60..199669.18 rows=11 width=20) (actual time=11714.504..31523.640 rows=1 loops=1)
              Recheck Cond: (("clientid" = 2565) AND ("time_job" > 1382496599))
              Filter: (("title")::"text" ~~* '%Hislop%'::"text")
              Rows Removed by Filter: 207654
              Buffers: shared hit=26942 read=101574 dirtied=438
              ->  Bitmap Index Scan on "ix_jobs_client_times"  (cost=0.00..4850.60 rows=155054 width=0) (actual time=11670.956..11670.956 rows=215142 loops=1)
                    Index Cond: (("clientid" = 2565) AND ("time_job" > 1382496599))
                    Buffers: shared hit=121 read=5772
Total runtime: 31524.874 ms

DISTINCTと左側の%を削除した後:

explain (analyze, buffers)
SELECT title  
FROM ja_jobs
WHERE title ILIKE 'Hislop 13035%'
AND clientid = 2565
AND time_job > 1382496599
ORDER BY title
LIMIT 10;


Limit  (cost=2275.53..2275.55 rows=9 width=20) (actual time=3492.479..3492.483 rows=1 loops=1)
  Buffers: shared hit=4940 read=448
  I/O Timings: read=83.285
  ->  Sort  (cost=2275.53..2275.55 rows=9 width=20) (actual time=3492.475..3492.477 rows=1 loops=1)
        Sort Key: "title"
        Sort Method: quicksort  Memory: 25kB
        Buffers: shared hit=4940 read=448
        I/O Timings: read=83.285
        ->  Bitmap Heap Scan on "ja_jobs"  (cost=391.62..2275.38 rows=9 width=20) (actual time=3492.460..3492.462 rows=1 loops=1)
              Recheck Cond: (("title")::"text" ~~* 'Hislop Street Clinic 2513035%'::"text")
              Filter: (("time_job" > 1382496599) AND ("clientid" = 2565))
              Buffers: shared hit=4940 read=448
              I/O Timings: read=83.285
              ->  Bitmap Index Scan on "ix_jobs_trgm_gin"  (cost=0.00..391.62 rows=482 width=0) (actual time=3492.427..3492.427 rows=1 loops=1)
                    Index Cond: (("title")::"text" ~~* 'Hislop 13035%'::"text")
                    Buffers: shared hit=4939 read=448
                    I/O Timings: read=83.285
Total runtime: 3492.531 ms
  • ご覧のとおり、クエリは新しいインデックスにヒットしていますが、速度が遅くなっています。

次にORDER BYを削除しましたが、クエリはまだ遅いです。

また、LIKEを使用しようとしましたが(これははるかに高速です)、LIKEは大文字と小文字を区別するため、行が返されませんでした。使えません。

2
user83914

インデックスがたくさんあります。あなたはそれらすべてが必要だとは思いません。それらがすべて使用されているかどうかを確認してください。マニュアル、章 Examining Index Usage の説明。

システムが 統計を収集するように構成されている である場合、特に調査することが明らかになります。

_SELECT * FROM pg_stat_user_indexes
_

これらの統計はpgAdminにも表示されます。

いくつかのインデックスは、次のように特に奇妙です。"ix_ja_jobs_gsdi_pk" "btree" (("id"::"text"))-インデックス付けのためにbigint idをtextにキャストするのはなぜですか?

役に立たないインデックスは、読み取りパフォーマンスを大幅に低下させることはありませんが、書き込みパフォーマンスと一般的なメンテナンスに負担をかけます。


クエリの主要な困難は、さまざまな述語の選択性を推定するです。これは、bigintclientidおよび_time_job_の場合は比較的単純ですが、パターンマッチング(_title ILIKE 'Hislop 13035%'_)の場合はhardです。

あなたのケース(UPDATE 3)では、Postgresはパターンに一致する482行を見つけると推定していますが、それは単一の行であることがわかります:

「ix_jobs_trgm_gin」でのビットマップインデックススキャン(コスト= 0.00..391.62 rows = 482 width = 0)(実際の時間= 3492.427..3492.427 rows = 1 loops = 1)

クエリのチューニングは全体像に依存します:カーディナリティ、データ分散、ハードウェア、負荷、同時実行、クエリの頻度、優先度...

関連する列の統計ターゲットを増やすのに役立ちますかもしれません

_ALTER ja_jobs
   ALTER clientid SET STATISTICS 1000
 , ALTER time_job SET STATISTICS 1000
 , ALTER title    SET STATISTICS 1000;
_

次に:_ANALYZE ja_jobs;_しかし、あまり期待しないでください。詳細:

浮動LIKEパターンの選択性の推定はhardです。左アンカーの方が簡単です。これら2つのかなり異なるケースを自由に組み合わせることができます。_ILIKE '%RYAN WER%'_(先頭のワイルドカードを含む)は_ILIKE 'Hislop 13035%'_よりもはるかに複雑です。概要:

最新バージョンには改善点がありますが、最大の改善点はPostgres 9.6(現在はベータ版)とその新しいバージョンのpg_trgmモジュールに付属しています。 ここでリリースノートを検討してください。 関連:

質問にないすべての情報に応じて、パフォーマンスを向上させる他のさまざまな方法があります。多分、複数列のインデックス、または部分インデックスまたはクエリのCTEです。

パフォーマンス調整に関する一般的なアドバイスもすべて適用されます。

高価なDISTINCTはすでに削除されています。 _ORDER BY title_が必要ない場合は、クエリプランが完全に変更され、それも削除される可能性があります。これらの両方が削除されると、Postgresは最初の10個の一致を選択して残りを無視できます。それ以外の場合、allの一致を見つけて検討する必要があります。 muchより高くなる可能性があります。試してください:

_SELECT title
FROM   ja_jobs
WHERE  title ILIKE 'Hislop 13035%'
AND    clientid = 2565
AND    time_job > 1382496599
LIMIT  10;  -- no ORDER BY
_

実際にleft-anchored LIKEパターンのみを処理する場合(_'Hislop 13035%'_のようなワイルドカードを追跡するが、_'%RYAN WER%'_は追跡しない)、非常に高速に使用できます_varchar_pattern_ops_インデックス。詳細な説明:

そう:

_CREATE INDEX ix_ja_jobs_special_idx ON public.ja_jobs
(clientid, title varchar_pattern_ops, time_job);
_

thisの順序で列にインデックスを付けます。最初は平等、後で範囲。説明:

このソリューションを拡張して、ILIKEを機能要素でカバーできます。

_CREATE INDEX ix_ja_jobs_special_idx ON public.ja_jobs
(clientid, lower(title) varchar_pattern_ops, time_job);_

そしてクエリを適応させます:

_SELECT title
FROM   ja_jobs
WHERE  lower(title) LIKE lower('Hislop 13035%')
AND    clientid = 2565
AND    time_job > 1382496599
LIMIT  10;_
3

2つの分析はテキスト比較が異なります。最初の分析は~~*、つまり大文字と小文字を区別しない(ILIKE)を示し、2番目の分析は~~のみ、つまり大文字と小文字を区別する(誤ってLIKEを実行していないかどうかを確認します) 2回目)。