我的数据库中有一个名为“mytable”的表,其中包含一个带有 JSONB 数据的“日期”字段,如下所示:
{
...
"myfield": "value1"
}
“myfield”字段当前可以具有值“value1”、“value2”和“value3”。此外,“myfield”可以为空,或者它可能根本不存在。
在我的查询中,我需要查找“myfield”不等于“value1”的行。
select * from mytable where data->>'myfield' <> 'value1'
我在“mytable”表上创建了部分索引以加快查询速度:
CREATE INDEX "idx_mytable_myfield" ON "mytable" USING btree (
((data->>'myfield'::text) COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
) WHERE
(data->>'myfield'::text)::text <> 'value1'::text
)
但是,查询并未使用该索引。同时,查询的选择性很高,顺序扫描速度明显慢。
如果我重写查询和索引以使用“in”而不是“<>”,则索引将在查询中使用。但这意味着将来添加新值时我必须重写查询和索引:
select * from mytable where (data->>'myfield'::text)::text in ('value2'::text,'value3'::text)
你能帮我理解我做错了什么吗?有没有办法重写查询和/或索引?
提前解决可能出现的问题:
ANALYZE mytable
。附注 我尝试向索引和查询添加附加条件,但没有帮助:
...
and data is not null
and data->'myfield'::text is not null
CREATE INDEX "mytable_myfield_idx" ON "set10"."mytable" USING btree (
("data" ->> 'myfield'::text) COLLATE "pg_catalog"."default" "pg_catalog"."text_ops" ASC NULLS LAST
)
WHERE ("data" ->> 'myfield'::text <> 'нет');
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, COSTS)
SELECT
*
FROM
mytable
WHERE
"data" ->> 'myfield'::text <> 'нет'
Seq Scan on set10.mytable (cost=0.00..2041.22 rows=86911 width=35) (actual time=19.098..35.258 rows=74 loops=1)
Output: "jiraKey", data, created
Filter: ((mytable.data ->> 'myfield'::text) <> 'нет'::text)
Rows Removed by Filter: 87274
Buffers: shared hit=731
Planning Time: 0.051 ms
Execution Time: 35.278 ms
我的实验表明问题出在“<>”中,优化器在这种情况下不会使用索引。
更新
如果我强制禁用 seqscan - 优化器会正确使用我的索引。由于某种原因,它认为使用 seqscan 更好,但这是错误的
SET enable_seqscan = false;
EXPLAIN (ANALYZE,BUFFERS, COSTS, VERBOSE)
select
*
FROM
mytable
WHERE
"data"->>'myfield' <> 'нет'
Bitmap Heap Scan on set10.mytable (cost=30.20..2064.86 rows=86911 width=35) (actual time=0.024..0.098 rows=74 loops=1)
Output: "jiraKey", data, created
Recheck Cond: ((mytable.data ->> 'myfield'::text) <> 'нет'::text)
Heap Blocks: exact=52
Buffers: shared hit=53
-> Bitmap Index Scan on mytable_myfield_idx (cost=0.00..8.47 rows=86911 width=0) (actual time=0.013..0.014 rows=74 loops=1)
Buffers: shared hit=1
Planning Time: 0.059 ms
Execution Time: 0.128 ms
为了让它准确估计行数,您需要这样的表达式索引:
create index on mytable ((data->>'myfield'));
创建后进行ANALYZE。您已经拥有部分形式的表达式索引,但规划器不使用部分索引来派生行计数。
如果您使用的是该软件的现代版本,您可以创建表达式的扩展统计数据:
create statistics asldfj on (data->>'myfield') from mytable;
这将生成与索引相同的统计信息,但没有与索引相同的存储或维护需求。但需要v14以上版本。