Chi*_*lax 3 postgresql indexing database-design postgresql-performance jsonb
为了搜索jsonb列中的特定键,我想在该列上创建索引。
使用:Postgres 10.2
忽略一些不相关的列,我有包含animals这些列的表(省略一些不相关的列):
animalid PK number
location (text)
type (text)
name (text)
data (jsonb) for eg: {"age": 2, "tagid": 11 }
Run Code Online (Sandbox Code Playgroud)
我需要根据:location、type和进行搜索tagId。喜欢:
where location = ? and type = 'cat' and (data ->> 'tagid') = ?
Run Code Online (Sandbox Code Playgroud)
其他要点:
如何确保搜索速度快?我考虑过的选项:
animal_id, location, tagId(尽管无法 FK 到分区父表)location在和typejsonb 键上创建索引。tagId- 对于除猫之外的所有动物,该列将为空。我确实在表上的其他列上有一个索引 - 但对如何创建索引以基于快速搜索猫有点困惑tagid。有什么建议么?
更新(忽略分区):
(在分区表上测试)
所以我决定采用 Erwin 建议的选项并尝试创建索引
CREATE INDEX ON animals_211 (location, ((data->>'tagid')::uuid)) WHERE type = 'cat';
Run Code Online (Sandbox Code Playgroud)
并尝试对查询进行解释(使用分区表以保持简单):
explain select * from animals_211 a
where a.location = 32341
and a.type = 'cat'
and (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c'
Run Code Online (Sandbox Code Playgroud)
从结果来看,它似乎没有使用创建的索引并进行顺序扫描:
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
Run Code Online (Sandbox Code Playgroud)
更新2(不使用部分索引)
它在某种程度上似乎是部分索引,就像没有它一样 - 它似乎有效:
CREATE INDEX tag_id_index ON animals_211 (location, type, ((data->>'tagid')::uuid))
Run Code Online (Sandbox Code Playgroud)
当我做解释计划时:
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))
Run Code Online (Sandbox Code Playgroud)
根据您的三个“要点”,我建议对表达式建立部分索引:
CREATE INDEX ON animals ((data->>'tagid'))
WHERE type = 'cat';
Run Code Online (Sandbox Code Playgroud)
用于CREATE INDEX CONCURRENTLY ...避免对同一表的并发写入访问出现锁定问题。
Postgres 还收集部分索引的特定统计信息,这有助于查询规划器获得适当的估计。请注意,如果您在创建后立即测试索引,则需要手动运行ANALYZE(或)才能启动。请参阅:VACUUM ANALYZEautovacuum
如果tagid确实是除 之外的其他数据类型text,您还可以转换表达式以进行更多优化。看:
您的更新建议tagid存储UUID值。读:
所以考虑这个索引:
CREATE INDEX ON animals (((data->>'tagid')::uuid)) -- !
WHERE type = 'cat';
Run Code Online (Sandbox Code Playgroud)
(data->>'tagid')::uuid需要额外的括号来使语法明确。
以及匹配的查询:
SELECT *
FROM animals
WHERE location = 32341
AND type = 'cats'
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c'; -- !
Run Code Online (Sandbox Code Playgroud)
或者 - 根据每个谓词的选择性以及可能的查询变体 - includelocation使其成为多列索引:
CREATE INDEX ON animals (location, ((data->>'tagid')::uuid))
WHERE type = 'cat';
Run Code Online (Sandbox Code Playgroud)
或者tagid首先,如果您有未按位置过滤的查询。看:
由于只有相对较少的行属于“cat”类型,因此索引将相对较小,不包括大部分“数百万行”。tagid我们一开始只需要为猫打开索引。双赢。
如果可能,将 json 键分解data->>'tagid'为专用列。(就像您考虑的选项3一样。)在不适用的情况下可以为 null,空存储非常便宜。然而,使存储和索引更便宜,并且查询更简单。
Postgres 10不支持分区表的父表上的索引。这是在 Postgres 11 中添加的。此后声明性分区得到了很大的改进。考虑升级到当前版本 13 或更高版本。
还有带有继承的“旧式”分区选项。然后,您可以为猫设置一个单独的分区,并仅在那里添加一个附加列tagid。手册:
对于声明性分区,分区必须具有与分区表完全相同的列集,而对于表继承,子表可能具有父表中不存在的额外列。
听起来非常合适。但继承已经不再受到 Postgres 的青睐,所以在这样做之前我会三思而后行。
无论哪种方式 - 无论是声明式还是继承 - 如果您将所有“猫”放在单独的分区中,则非部分索引可以完成这项工作,显然:
CREATE INDEX ON cats (location, ((data->>'tagid')::uuid));
Run Code Online (Sandbox Code Playgroud)
并且查询可以针对分区cats而不是父表:
SELECT *
FROM cats
WHERE location = 32341
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c';
Run Code Online (Sandbox Code Playgroud)
以父表为目标应该也可以。(不确定 Postgres 10。)
SELECT *
FROM animals
WHERE type = 'cat'
AND location = 32341
AND (data->>'tagid')::uuid = '5e54c1d9-3ea0-4bca-81d6-1000d90cc42c';
Run Code Online (Sandbox Code Playgroud)
但为此激活分区修剪。手册:
请注意,分区修剪仅由分区键隐式定义的约束驱动,而不是由索引的存在驱动。因此没有必要在键列上定义索引。
所有其他分区都应该被修剪,然后你应该cats只在该分区上进行索引扫描......
| 归档时间: |
|
| 查看次数: |
3337 次 |
| 最近记录: |