web-dev-qa-db-ja.com

PostgreSQL演算子はインデックスを使用しますが、基になる関数は使用しません

JDBCでJSONBを使用しようとしています。つまり、「?」を使用する演算子を回避する必要があります。文字(PostgreSQL JDBCドライバーにはこの文字のエスケープがないため)。簡単な表をとります:

CREATE TABLE jsonthings(d JSONB NOT NULL);
INSERT INTO jsonthings VALUES
    ('{"name":"First","tags":["foo"]}')
  , ('{"name":"Second","tags":["foo","bar"]}')
  , ('{"name":"Third","tags":["bar","baz"]}')
  , ('{"name":"Fourth","tags":["baz"]}');
CREATE INDEX idx_jsonthings_name ON jsonthings USING GIN ((d->'name'));

コマンドラインを使用して、単純な選択を実行でき、期待どおりにインデックスを使用します。

EXPLAIN ANALYZE SELECT d FROM jsonthings WHERE d->'name' ? 'First';

                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on jsonthings  (cost=113.50..30236.13 rows=10000 width=61) (actual time=0.024..0.025 rows=1 loops=1)
   Recheck Cond: ((d -> 'name'::text) ? 'First'::text)
   Heap Blocks: exact=1
   ->  Bitmap Index Scan on idx_jsonthings_name  (cost=0.00..111.00 rows=10000 width=0) (actual time=0.015..0.015 rows=1 loops=1)
         Index Cond: ((d -> 'name'::text) ? 'First'::text)
 Planning time: 0.073 ms
 Execution time: 0.047 ms
(7 rows)

?文字を使用できないため、?演算子を支えるfunctionを使用しました。ただし、インデックスは使用していません。

EXPLAIN ANALYZE SELECT d FROM jsonthings WHERE jsonb_exists(d->'name','First');
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on jsonthings  (cost=10000000000.00..10000263637.06 rows=3333334 width=61) (actual time=0.016..3135.119 rows=1 loops=1)
   Filter: jsonb_exists((d -> 'name'::text), 'First'::text)
   Rows Removed by Filter: 10000003
 Planning time: 0.051 ms
 Execution time: 3135.138 ms
(5 rows)

なぜこれが起こっているのですか?関数にインデックスを使用させるにはどうすればよいですか?実際にはテーブルには別の10MM行があり、enable_seqscanもオフになっているので、プランナがインデックスを使用しないと決定した場合とは異なります。

コメントに応じて、代わりにcustom operatorを使用してみました:

CREATE OPERATOR ### (
  PROCEDURE = jsonb_exists,
  LEFTARG = jsonb,
  RIGHTARG = text,
  RESTRICT = contsel,
  JOIN = contjoinsel);

しかし、これには同じ問題があります。

EXPLAIN ANALYZE SELECT d FROM jsonthings WHERE d->'name' ### 'First';
                                                           QUERY PLAN                                                           
--------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on jsonthings  (cost=10000000000.00..10000263637.06 rows=10000 width=61) (actual time=0.012..3381.608 rows=1 loops=1)
   Filter: ((d -> 'name'::text) ### 'First'::text)
   Rows Removed by Filter: 10000003
 Planning time: 0.046 ms
 Execution time: 3381.623 ms
(5 rows)

更新

最新のPostgreSqlドライバー(2015年3月現在)は?文字をエスケープする機能を備えているため、この特定のケースは問題ではなくなりました。

6
jgm

サポートされている演算子と回避策

json列のGINインデックスのデフォルトの演算子クラス_jsonb_ops_は、これらの演算子のみをサポートします (ドキュメントごと):

_Name        Indexed Data Type   Indexable Operators
...
jsonb_ops   jsonb               ? ?& ?| @>
_

これは逆の方法でも実現できます。_?_演算子を使用して、単純なIMMUTABLE SQL関数を作成します。インライン化し、演算子自体と同じようにインデックスを使用します。

_CREATE OR REPLACE FUNCTION jb_contains(jsonb, text)
  RETURNS bool AS
'SELECT $1 ? $2' LANGUAGE sql IMMUTABLE;
_

これは機能し、Postgres 9.4でテストしました...

誤解

ただし、あなたは間違った質問を尋ねてきました。質問には2つの基本的な誤解があります。

  1. jsonb演算子_?_は、valuesの検索には使用できません。 keysまたはarray elementsの場合のみ。 マニュアル:

    説明:
    文字列は、JSON値内のトップレベルキーとして存在しますか?

    間違った演算子を取得しました、WHERE条件は機能しません: _WHERE d->'name' ? 'First'_

  2. あなたが持っている式インデックスはどちらの方法でも意味がありません

    CREATE INDEX idx_jsonthings_name ON jsonthings USING GIN ((d->'name'));
    • 式_d->'name'_はjsonb値を返します。 dとして値を取得するには、text_->>__'name'_が必要です。

    • しかし、それはまだ無意味です。 nameキーの値は単純な文字列なので、GINインデックス(可能な場合)はそもそも意味がありません。

ソリューション

演算子_?_は不要なので、回避策もありません。
実際に機能する2つの方法を次に示します。

  1. dのプレーンGINインデックスと "contains"演算子を使用_@>_

    _CREATE INDEX idx_jsonthings_d_gin ON jsonthings USING GIN (d);
    
    SELECT d FROM jsonthings WHERE d @> '{"name":"First"}'
    _

    さらに 特殊な演算子クラス_jsonb_path_ops_ を使用することもできます。見る:

  2. _d->>'email'_のBツリー式インデックスと古き良きテスト_=_

    _CREATE INDEX idx_jsonthings_d_email ON jsonthings ((d->>'email'));
    
    SELECT d FROM jsonthings WHERE d->>'email' = 'First';
    _

2番目のインデックスはかなり小さくなり、クエリは高速になります。

3