结合其他列索引 JSONB 键
Indexing a JSONB key in combination with other columns
为了搜索 jsonb
列中的特定键,我想在该列上创建索引。
使用:Postgres 10.2
忽略一些不相关的列,我有 table 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,这是现在添加的新动物类型。
- 整个table中“猫”的数量会比其他种类的动物少
- table 很大,有数百万行 - 并且已分区。
如何确保搜索速度快?我考虑过的选项:
- 制作一个单独的 table 猫来存储:
animal_id
、location
、tagId
(尽管 FK 到分区父 table 是不可能的)
- 在
location
、type
和 jsonb 键上创建索引。
- 创建一个新的(索引的)列
tagId
- 对于除猫以外的所有动物来说都是空的。
我在 table 的其他列上确实有一个索引 - 但对于如何创建索引以基于 tagid
快速搜索猫有点困惑。有什么建议吗?
UPDATE(忽略分区):
(在分区 table 上测试)
所以我决定采用 Erwin 建议的选项并尝试创建索引
CREATE INDEX ON animals_211 (location, ((data->>'tagid')::uuid)) WHERE type = 'cat';
并尝试对查询进行 EXPLAIN(使用分区 table 以保持简单):
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
更新 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 ...
避免对同一 table.
的并发写入访问的锁定问题
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 存储非常便宜。使存储和索引更便宜,并且查询更简单。
- Does not using NULL in PostgreSQL still use a NULL bitmap in the header?
分区
Postgres 10 不支持分区 table 的父 table 上的索引。这是在 Postgres 11 中添加的。声明性分区已得到改进很多。考虑升级到当前版本 13 或更高版本。
还有“老式”选项partitioning with inheritance. Then you can have a separate partition for cats with an additional column tagid
only there. The manual:
For declarative partitioning, partitions must have exactly the same set of columns as the partitioned table, whereas with table inheritance, child tables may have extra columns not present in the parent.
听起来很合适。但是继承已经不受 Postgres 的青睐,所以我会三思而后行。
无论哪种方式 - 无论是声明式还是继承式 - 如果您将所有“猫”都放在一个单独的分区中,则非部分索引可以完成这项工作,显然:
CREATE INDEX ON cats (location, ((data->>'tagid')::uuid));
并且查询可以针对分区 cats
而不是父分区 table:
SELECT *
FROM cats
WHERE location = 32341
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c';
以父 table 为目标也应该有效。 (不确定 Postgres 10。)
SELECT *
FROM animals
WHERE type = 'cat'
AND location = 32341
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c';
但为此激活 partition pruning。手册:
Note that partition pruning is driven only by the constraints defined
implicitly by the partition keys, not by the presence of indexes.
Therefore it isn't necessary to define indexes on the key columns.
应该对所有其他分区进行 p运行ed,然后您应该只对 cats
分区进行索引扫描 ...
为了搜索 jsonb
列中的特定键,我想在该列上创建索引。
使用:Postgres 10.2
忽略一些不相关的列,我有 table 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,这是现在添加的新动物类型。
- 整个table中“猫”的数量会比其他种类的动物少
- table 很大,有数百万行 - 并且已分区。
如何确保搜索速度快?我考虑过的选项:
- 制作一个单独的 table 猫来存储:
animal_id
、location
、tagId
(尽管 FK 到分区父 table 是不可能的) - 在
location
、type
和 jsonb 键上创建索引。 - 创建一个新的(索引的)列
tagId
- 对于除猫以外的所有动物来说都是空的。
我在 table 的其他列上确实有一个索引 - 但对于如何创建索引以基于 tagid
快速搜索猫有点困惑。有什么建议吗?
UPDATE(忽略分区):
(在分区 table 上测试)
所以我决定采用 Erwin 建议的选项并尝试创建索引
CREATE INDEX ON animals_211 (location, ((data->>'tagid')::uuid)) WHERE type = 'cat';
并尝试对查询进行 EXPLAIN(使用分区 table 以保持简单):
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
更新 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 ...
避免对同一 table.
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 存储非常便宜。使存储和索引更便宜,并且查询更简单。
- Does not using NULL in PostgreSQL still use a NULL bitmap in the header?
分区
Postgres 10 不支持分区 table 的父 table 上的索引。这是在 Postgres 11 中添加的。声明性分区已得到改进很多。考虑升级到当前版本 13 或更高版本。
还有“老式”选项partitioning with inheritance. Then you can have a separate partition for cats with an additional column tagid
only there. The manual:
For declarative partitioning, partitions must have exactly the same set of columns as the partitioned table, whereas with table inheritance, child tables may have extra columns not present in the parent.
听起来很合适。但是继承已经不受 Postgres 的青睐,所以我会三思而后行。
无论哪种方式 - 无论是声明式还是继承式 - 如果您将所有“猫”都放在一个单独的分区中,则非部分索引可以完成这项工作,显然:
CREATE INDEX ON cats (location, ((data->>'tagid')::uuid));
并且查询可以针对分区 cats
而不是父分区 table:
SELECT *
FROM cats
WHERE location = 32341
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c';
以父 table 为目标也应该有效。 (不确定 Postgres 10。)
SELECT *
FROM animals
WHERE type = 'cat'
AND location = 32341
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c';
但为此激活 partition pruning。手册:
Note that partition pruning is driven only by the constraints defined implicitly by the partition keys, not by the presence of indexes. Therefore it isn't necessary to define indexes on the key columns.
应该对所有其他分区进行 p运行ed,然后您应该只对 cats
分区进行索引扫描 ...