如何解决结合其他列索引 JSONB 键 基础知识忽略分区分区
为了搜索 jsonb
列中的特定键,我想在该列上创建一个索引。
使用:Postgres 10.2
忽略一些不相关的列,我的表 animals
包含这些列(省略了一些不相关的列):
animalid PK number
location (text)
type (text)
name (text)
data (jsonb) for eg: {"age": 2,"tagid": 11 }
我需要根据:location
、type
和 tagId
进行搜索。喜欢:
where location = ? and type = 'cat' and (data ->> 'tagid') = ?
其他要点:
- 只有猫类型的动物才会有标签 ID,这是现在添加的新动物类型。
- 与其他类型的动物相比,整个表格中“猫”的数量会更少。
- 该表很大,有数百万行 - 并且已分区。
如何确保搜索速度快?我考虑过的选项:
- 制作一个单独的表cats来存储:
animal_id
、location
、tagId
(虽然FK到分区的父表是不可能的) - 在
location
、type
和 jsonb 键上创建索引。 - 创建一个新的(索引)列
tagId
- 对于除猫之外的所有动物,该列都为空。
我确实在表的其他列上有一个索引 - 但对如何创建索引以快速基于 tagid
搜索猫有点困惑。有什么建议吗?
UPDATE(忽略分区):
(在分区表上测试)
所以我决定采用 Erwin 建议的选项并尝试创建索引
CREATE INDEX ON animals_211 (location,((data->>'tagid')::uuid)) WHERE type = 'cat';
并在查询上尝试了 EXPLAIN(使用分区表以保持简单):
explain select * from animals_211 a
where a.location = 32341
and a.type = 'cat'
and (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c'
从结果来看,它似乎没有使用创建的索引并进行顺序扫描:
Seq Scan on animals_211 e (cost=0.00..121.70 rows=1 width=327) |
Filter: ((location = 32341) AND ((type)::text = 'cat'::text) AND (((data ->> 'tagid'::text))::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c'::uuid
UPDATE 2(不使用部分索引)
不知何故,它似乎是部分索引,因为没有它 - 它似乎有效:
CREATE INDEX tag_id_index ON animals_211 (location,type,((data->>'tagid')::uuid))
当我做一个解释计划时:
Index Scan using tag_id_index on animals_211 e (cost=0.28..8.30 rows=1 width=327)
Index Cond: ((location = 32341) AND ((type)::text = 'cat'::text) AND (((data ->> 'tagid'::text))::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c'::uuid))
解决方法
基础知识(忽略分区)
根据您的三个“重要点”,我建议在表达式上使用 partial index:
CREATE INDEX ON animals ((data->>'tagid'))
WHERE type = 'cat';
使用 CREATE INDEX CONCURRENTLY ...
可避免对同一表进行并发写入访问的锁定问题。
Postgres 还为部分索引收集特定的统计信息,这有助于查询规划器获得适当的估计。 注意,如果您在创建索引后在 ANALYZE
启动之前立即测试它,您需要手动运行 VACUUM ANALYZE
(或 autovacuum
)。请参阅:
- PostgreSQL partial index unused when created on a table with existing data
- Index that is not used,yet influences query
如果 tagid
确实是除 text
之外的其他数据类型,您还可以强制转换表达式以进一步优化。见:
您的更新建议tagid
存储UUID值。阅读:
- Would index lookup be noticeably faster with char vs varchar when all values are 36 chars
- What is the optimal data type for an MD5 field?
所以考虑这个索引:
CREATE INDEX ON animals (((data->>'tagid')::uuid)) -- !
WHERE type = 'cat';
需要在 (data->>'tagid')::uuid
周围加上一组额外的括号才能使语法无歧义。
还有一个匹配的查询:
SELECT *
FROM animals
WHERE location = 32341
AND type = 'cats'
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c'; -- !
或者 - 取决于每个谓词的选择性以及查询的哪些变体是可能的 - 包括 location
以使其成为多列索引:
CREATE INDEX ON animals (location,((data->>'tagid')::uuid))
WHERE type = 'cat';
或者 tagid
如果您有没有过滤位置的查询。见:
由于只有相对较少的行属于“cat”类型,因此索引将相对较小,不包括“数百万行”的大部分。我们只需要 tagid
上的索引让猫开始。双赢。
如果可能,将 json 键 data->>'tagid'
拆分为专用列。 (就像您认为的选项 3.)在不适用的情况下可以为 null,null 存储非常便宜。使存储和索引更便宜,而且查询更简单。
分区
Postgres 10 不支持分区表的父表上的索引。这是在 Postgres 11 中添加的。自那以后,声明式分区得到了很多的改进。考虑升级到当前版本 13 或更高版本。
还有“旧式”partitioning with inheritance 选项。然后你可以有一个单独的猫分区,只有在那里有一个额外的列 tagid
。 The manual:
对于声明式分区,分区必须具有与分区表完全相同的列集,而对于表继承,子表可能具有父表中不存在的额外列。
听起来很合适。但是继承已经不受 Postgres 的青睐,所以我在做之前会三思。
无论哪种方式 - 无论是声明式还是继承式 - 如果您在单独的分区中拥有所有“猫”,显然非部分索引可以完成这项工作:
CREATE INDEX ON cats (location,((data->>'tagid')::uuid));
并且查询可以针对分区 cats
而不是父表:
SELECT *
FROM cats
WHERE location = 32341
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c';
定位父表也应该有效。 (不确定 Postgres 10。)
SELECT *
FROM animals
WHERE type = 'cat'
AND location = 32341
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c';
但是为此激活 partition pruning。说明书:
请注意,分区修剪仅由定义的约束驱动 隐式地由分区键,而不是索引的存在。 因此不需要在键列上定义索引。
应该修剪所有其他分区,然后您应该只对 cats
分区进行索引扫描...
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。