PostgreSQL 运算符使用索引但底层函数不使用

jgm*_*jgm 6 postgresql index-tuning json operator postgresql-9.4

我正在尝试JSONB与 JDBC一起使用,这意味着我必须避免使用任何使用 '?' 的运算符。字符(因为 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'));
Run Code Online (Sandbox Code Playgroud)

使用命令行我可以运行一个简单的选择并按预期使用索引:

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)
Run Code Online (Sandbox Code Playgroud)

由于我无法使用该?字符,所以我求助于使用支持运算符的函数?。但是,它没有使用索引:

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)
Run Code Online (Sandbox Code Playgroud)

为什么会发生这种情况,我该怎么做才能使函数使用索引?请注意,实际上该表中还有另外 10 MM 行,我也已enable_seqscan关闭,因此这不是规划器决定不使用索引的情况。

为了回应评论,我尝试使用自定义运算符

CREATE OPERATOR ### (
  PROCEDURE = jsonb_exists,
  LEFTARG = jsonb,
  RIGHTARG = text,
  RESTRICT = contsel,
  JOIN = contjoinsel);
Run Code Online (Sandbox Code Playgroud)

但这有同样的问题:

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)
Run Code Online (Sandbox Code Playgroud)

更新

最新的 PostgreSql 驱动程序(截至 2015 年 3 月)能够对?字符进行转义,因此这种特定情况不再是问题。

Erw*_*ter 4

支持的运营商和解决方法

json列上 GIN 索引的默认运算符类jsonb_ops仅支持这些运算符(根据文档):

Name        Indexed Data Type   Indexable Operators
...
jsonb_ops   jsonb               ? ?& ?| @>
Run Code Online (Sandbox Code Playgroud)

您可以通过其他方式实现此目的:使用运算符创建一个简单的IMMUTABLESQL?函数,该函数可以内联,并且将像运算符本身一样使用索引:

CREATE OR REPLACE FUNCTION jb_contains(jsonb, text)
  RETURNS bool AS
'SELECT $1 ? $2' LANGUAGE sql IMMUTABLE;
Run Code Online (Sandbox Code Playgroud)

这有效,我在 Postgres 9.4 中测试过......

误会

然而,你一直在问错误的问题。你的问题中有两个基本的误解。

  1. jsonb运算符?不能用于搜索。仅适用于数组元素手册:

    描述:
    该字符串是否作为 JSON 值中的顶级键存在?

    您输入了错误的运算符,该WHERE条件不起作用:WHERE d->'name' ? 'First'

  2. 你所拥有的表达式索引无论如何都没有意义

    CREATE INDEX idx_jsonthings_name ON jsonthings USING GIN ((d->'name'));
    Run Code Online (Sandbox Code Playgroud)
    • 表达式d->'name'返回一个jsonb值。您需要获取值为.d->>'name'text

    • 但这仍然毫无意义。由于键的值name是一个简单的字符串,因此 GIN 索引(虽然可能)一开始就没有意义。

解决方案

您不需要操作员?- 所以也没有解决方法。
以下是两种实际可行的方法:

  1. 普通 GIN 索引d并使用“包含”运算符@>

    CREATE INDEX idx_jsonthings_d_gin ON jsonthings USING GIN (d);
    
    SELECT d FROM jsonthings WHERE d @> '{"name":"First"}'
    
    Run Code Online (Sandbox Code Playgroud)

    您甚至可以使用更专业的运算符类jsonb_path_ops。看:

  2. B 树表达式索引d->>'email'并用 good old 进行测试=

    CREATE INDEX idx_jsonthings_d_email ON jsonthings ((d->>'email'));
    
    SELECT d FROM jsonthings WHERE d->>'email' = 'First';
    
    Run Code Online (Sandbox Code Playgroud)

第二个索引会小得多并且查询速度更快。

  • @jgm:[你的新问题](http://dba.stackexchange.com/q/102910/3684)让我再次审视这个问题。考虑完全重写的答案。 (2认同)