我发现当我在查询中使用 jsonb_* 函数时无法使 Postgres 使用 GIN 索引的问题。 jsonb_ops 和 jsonb_path_ops 运算符类都存在问题。
我们先做好准备。 创建表
CREATE TABLE applications
(
id TEXT PRIMARY KEY,
application JSONB
);
CREATE INDEX ON applications USING gin (application jsonb_path_ops);
让我们用一些数据填充表(以使 Postgres 使用 GIN 索引)
INSERT INTO applications(id, application)
VALUES ('1', '{
"type_code": 1,
"persons": [
{
"type_code": 4,
"firstname": "John",
"lastname": "Doe"
}
]
}');
INSERT INTO applications (SELECT i, a.application FROM applications a, generate_series(2, 100000) i);
然后我尝试选择如下数据:
EXPLAIN ANALYZE
SELECT * FROM applications
WHERE applications.application @? '$.persons[*] ? (@.type_code == 3)';
注意使用了GIN索引
-- Bitmap Heap Scan on applications (cost=64.00..68.01 rows=1 width=130) (actual time=0.410..0.419 rows=0 loops=1)
-- " Recheck Cond: (application @? '$.""persons""[*]?(@.""type_code"" == 3)'::jsonpath)"
-- -> Bitmap Index Scan on applications_application_idx (cost=0.00..64.00 rows=1 width=0) (actual time=0.095..0.096 rows=0 loops=1)
-- " Index Cond: (application @? '$.""persons""[*]?(@.""type_code"" == 3)'::jsonpath)"
-- Planning Time: 1.493 ms
-- Execution Time: 0.861 ms
现在我尝试选择这样的数据:
EXPLAIN ANALYZE
SELECT * FROM applications
WHERE jsonb_path_exists(
applications.application,
'$.persons[*] ? (@.type_code == 3)'
);
可以看到本例中没有使用GIN索引:
-- Aggregate (cost=3374.33..3374.34 rows=1 width=8) (actual time=114.048..114.055 rows=1 loops=1)
-- -> Seq Scan on applications (cost=0.00..3291.00 rows=33333 width=0) (actual time=0.388..109.580 rows=100000 loops=1)
-- " Filter: jsonb_path_exists(application, '$.""persons""[*]?(@.""type_code"" == 3)'::jsonpath, '{}'::jsonb, false)"
-- Planning Time: 1.514 ms
-- Execution Time: 114.674 ms
是否可以让 Postgres 在第二个查询中使用 GIN 索引?
使用 jsonb_* 函数对我来说是首选,因为我可以使用位置参数来构建查询:
SELECT * FROM applications
WHERE jsonb_path_exists(
applications.application,
'$.persons[*] ? (@.type_code == $person_type_code)',
jsonb_build_object('person_type_code', $1)
);
如何使 Postgres GIN 索引与 jsonb_* 函数一起工作?
你不能。 PostgreSQL 索引 与特定运算符类中的运算符相关联:
仅当您在定义索引时使用的操作类中使用运算符时,一般来说,PostgreSQL 索引可用于优化包含一个或多个以下形式的 WHERE 或 JOIN 子句的查询
indexed-column
indexable-operator
comparison-value
这里,
是定义了索引的任何列或表达式。indexed-column
是一个运算符,它是索引列的索引运算符类的成员。并且indexable-operator
可以是任何非易失性且不引用索引表的表达式。comparison-value
GIN才会对您有所帮助(默认为
jsonb_ops
):
的默认 GIN 运算符类支持使用键存在运算符jsonb
、?
和?|
、包含运算符?&
以及@>
匹配运算符jsonpath
和@?
.@@
进行查询
即使存在与这些运算符所做的等效的
jsonb_path_X()
函数,但仅当您使用运算符而不是函数时,索引才会启动。
在像 PostGIS 这样的情况下,函数实际上确实使用了索引,但那是因为它们包装了一个或添加了一个使用索引的基于运算符的条件,然后使用实际函数来重新检查预过滤的行。