web-dev-qa-db-ja.com

ts_query言語が列からフェッチされる場合、PostgreSQL GINインデックスは使用されません

私はいくつかの多言語コンテンツを格納するテーブルを持っています:

CREATE TABLE search (
  content text NOT NULL,
  language regconfig NOT NULL,
  fulltext tsvector
);

CREATE INDEX search_fulltext ON search USING GIN(fulltext);

INSERT INTO search (language, content) VALUES 
  ('dutch', 'Als achter vliegen vliegen vliegen vliegen vliegen vliegen achterna'),
  ('dutch', 'Langs de koele kali liep een kale koeli met een kilo kali op zijn kale koeli-kop.'),
  ('dutch', 'Moeder sneed zeven scheve sneden brood'),
  ('english', 'I saw Susie sitting in a shoe shine shop. Where she sits she shines, and where she shines she sits.'),
  ('english', 'How can a clam cram in a clean cream can?'),
  ('english', 'Can you can a can as a canner can can a can?');

UPDATE search SET fulltext = to_tsvector(language, content);

常に正しい言語で検索するために、次のクエリを使用します。

SELECT FROM search WHERE fulltext @@ to_tsquery(language, 'shine');
(1 row)

SELECT FROM search WHERE fulltext @@ to_tsquery(language, 'vlieg');
(1 row)

言語をハードコーディングすると正しい結果が得られないため:

SELECT FROM search WHERE fulltext @@ to_tsquery('dutch', 'shine');
(0 rows)

SELECT FROM search WHERE fulltext @@ to_tsquery('english', 'vlieg');
(0 rows)

ただし、問題は、PostgreSQLが最初のクエリセットを使用するときにGINインデックスを使用せず、代わりに順次スキャンを実行することです。

(注:行数が少ないため、これらの例ではSET enable_seqscan = OFF;を使用したスキャンを無効にしています)

EXPLAIN ANALYZE SELECT * FROM search WHERE fulltext @@ to_tsquery(language, 'shine');
---
Seq Scan on search  (cost=0.00..17.35 rows=2 width=136) (actual time=0.040..0.044 rows=1 loops=1)
    Filter: (fulltext @@ to_tsquery(language, 'shine'::text))
    Rows Removed by Filter: 5
Planning time: 0.039 ms
Execution time: 0.064 ms
(5 rows)

言語をハードコーディングするときは次のようになります。

EXPLAIN ANALYZE SELECT FROM search WHERE fulltext @@ to_tsquery('dutch', 'vlieg');
---
Bitmap Heap Scan on search  (cost=12.63..23.66 rows=82 width=0) (actual time=0.044..0.044 rows=1 loops=1)
  Recheck Cond: (fulltext @@ '''vlieg'''::tsquery)
  Heap Blocks: exact=1
  ->  Bitmap Index Scan on search_fulltext  (cost=0.00..12.61 rows=82 width=0) (actual time=0.037..0.037 rows=1 loops=1)
        Index Cond: (fulltext @@ '''vlieg'''::tsquery)
Planning time: 0.128 ms
Execution time: 0.065 ms
(7 rows)

だから私の質問です:ts_queryの列を使用して正しい言語構成を使用し、PostgresにGINインデックスを使用させることはまったく可能ですか?

PostgreSQL 9.4を使用しています。

[〜#〜]編集[〜#〜]

realテーブルの実行計画は次のとおりです。

言語構成の列を使用する:

Seq Scan on search  (cost=0.00..8727.25 rows=188 width=0) (actual time=0.725..352.307 rows=1689 loops=1)
  Filter: (fulltext @@ to_tsquery(language_config, 'example'::text))
  Rows Removed by Filter: 35928
Planning time: 0.053 ms
Execution time: 352.915 ms

言語をハードコーディングする場合:

Bitmap Heap Scan on search  (cost=28.65..4088.92 rows=1633 width=0) (actual time=0.514..10.475 rows=1684 loops=1)
  Recheck Cond: (fulltext @@ '''exampl'''::tsquery)
  Heap Blocks: exact=1522
  ->  Bitmap Index Scan on search_fulltext  (cost=0.00..28.24 rows=1633 width=0) (actual time=0.333..0.333 rows=1684 loops=1)
        Index Cond: (fulltext @@ '''exampl'''::tsquery)
Planning time: 0.180 ms
Execution time: 10.564 ms

EDIT#2

Postgres 9.5で試してみましたが、同じ結果です

3
jaap3

@ 3manuekは私に考えさせました...クエリがローに依存しないようにクエリを事前にローカライズするとどうなるでしょうか。だから私はこれを思いつきました:

SELECT 
  *
FROM 
  search s 
LEFT JOIN (
  SELECT 'english'::regconfig AS language, to_tsquery('english', 'vliegen') as q
  UNION ALL SELECT 'dutch'::regconfig AS language, to_tsquery('dutch', 'vliegen') as q
  UNION ALL SELECT 'simple'::regconfig AS language, to_tsquery('simple', 'vliegen') as q
) q ON (s.language=q.language) WHERE fulltext @@ q;

クエリプランは(実際のデータベースでは)次のようになります。

Nested Loop  (cost=205.36..1327.05 rows=188 width=1590) (actual time=3.726..7.045 rows=16 loops=1)
  ->  Append  (cost=0.00..0.06 rows=3 width=36) (actual time=0.001..0.006 rows=3 loops=1)
        ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.000..0.000 rows=1 loops=1)
        ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.002..0.002 rows=1 loops=1)
        ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.000..0.000 rows=1 loops=1)
  ->  Bitmap Heap Scan on search s  (cost=205.36..441.70 rows=63 width=1554) (actual time=2.323..2.331 rows=5 loops=3)
        Recheck Cond: ((fulltext @@ ('''vliegen'''::tsquery)) AND ((language)::oid = (('english'::regconfig))::oid))
        Heap Blocks: exact=16
        ->  BitmapAnd  (cost=205.36..205.36 rows=63 width=0) (actual time=2.316..2.316 rows=0 loops=3)
              ->  Bitmap Index Scan on search_fulltext  (cost=0.00..17.41 rows=188 width=0) (actual time=0.022..0.022 rows=9 loops=3)
                    Index Cond: (fulltext @@ ('''vliegen'''::tsquery))
              ->  Bitmap Index Scan on search_language  (cost=0.00..187.67 rows=12539 width=0) (actual time=2.284..2.284 rows=12539 loops=3)
                    Index Cond: ((language)::oid = (('english'::regconfig))::oid)
Planning time: 0.234 ms
Execution time: 7.088 ms

正しく動作しているようですが、自信がありません。

[〜#〜] edit [〜#〜] UNIONの代わりにUNION ALLを使用するように更新されました。これにより、サブクエリでの一意/並べ替えの必要がなくなります

編集2クエリプランナーでも使用される言語のインデックスを作成したようです。

CREATE INDEX search_language ON search USING BTREE(language);

これにより、このクエリが少し役立ちます。

編集 LEFT JOINを使用します。これは技術的にはより正確であり、クエリをクエリプランとより厳密に一致させることができます

1
jaap3

部分的な式インデックスを使用した解決策を提案します。

CREATE TABLE search (
   search_id serial PRIMARY KEY
 , language  regconfig NOT NULL  -- order of columns matters a bit
 , content   text NOT NULL
   --  *no* redundant fulltext tsvector
);

冗長なfulltext列がないため、テーブルが小さくなり、全体的なパフォーマンスが向上します。
関連する言語ごとに1つの部分式インデックスを作成します。

CREATE INDEX search_fulltext_dutch ON search USING GIN(to_tsvector('dutch', content))
WHERE language = 'dutch'::regconfig;
CREATE INDEX search_fulltext_english ON search USING GIN(to_tsvector('english', content))
WHERE language = 'english'::regconfig;
-- more?

すべての部分インデックスを合わせると、合計インデックスとほぼ同じ大きさになります。

次に、クエリのインデックス条件を一致させます。

SELECT * FROM search  -- does not return useless column fulltext now
WHERE  language = 'dutch'::regconfig  -- match partial index condition
AND    to_tsvector('dutch', content) @@ to_tsquery('dutch', 'vliegen')

UNION ALL
SELECT * FROM search
WHERE  language = 'english'::regconfig
AND    to_tsvector('english', content) @@ to_tsquery('english', 'vliegen');

-- more?

この方法でビットマップインデックスまたはインデックススキャンを取得します。
languageの別のインデックスは他の目的に役立つ場合があります。このクエリでは必要ありません。

1

to_tsqueryを使用してlanguage列をクエリするときに、Pgがseqscanを強制しているのは理にかなっていますすべての反復には、検査されたすべての行の言語列の値が必要です

この特定のケースでは、クエリの方法を変更することができます。

so=# EXPLAIN ANALYZE SELECT * FROM search WHERE 
to_tsvector(language,fulltext::text) @@ 'shine'::tsquery;

languageを使用して動的にクエリを実行しているので、オンザフライでto_tsvectorを発行したい場合があります。私はこのインデックスをテストしましたが、tsvectorにインデックスを付けるよりも少し高速です。 contentを使用してこれを行うこともできますが、解析には少し余分な時間がかかります(テキストのサイズによっては、実行が大きく異なる場合があります)。

CREATE EXTENSION btree_gin;
CREATE INDEX search_fulltext_new ON search USING 
GIN(to_tsvector(language,fulltext::text), language);

ここに説明があります:

so=# EXPLAIN ANALYZE SELECT * FROM search WHERE 
to_tsvector(language,fulltext::text) @@ 'shine'::tsquery;
Bitmap Heap Scan on search  (cost=12.95..410.41 rows=123 width=168) (actual time=0.652..9.256 rows=4096 loops=1)
   Recheck Cond: (to_tsvector(language, (fulltext)::text) @@ '''shine'''::tsquery)
   Heap Blocks: exact=512
    ->  Bitmap Index Scan on search_fulltext_new  (cost=0.00..12.92 rows=123 width=0) (actual time=0.584..0.584 rows=4096 loops=1)
     Index Cond: (to_tsvector(language, (fulltext)::text) @@ '''shine'''::tsquery)
 Planning time: 0.164 ms
 Execution time: 10.416 ms

また、言語でフィルタリングすることもできます:

so=# EXPLAIN ANALYZE SELECT * FROM search WHERE 
to_tsvector(language,fulltext::text) @@ 'shine'::tsquery AND language = 
'english'::regconfig;

値の分布によっては、テーブルを言語でパーティション分割することをお勧めします(これは、この問題を修正するためではなく、さらに最適化するためです)。そうすることで、この変換をその場で行う必要はなく、言語フィルタリングは代わりにconstraint exclusionによって行われ、親テーブルに対するseqscanを回避します。

0
3manuek