• 使用btree和gin建立索引在json列和字段上有哪些不同作用?
  • 发布于 2个月前
  • 540 热度
    0 评论
JSON或者JSONB一般使用btree和gin来建立索引,推荐使用gin。下面会介绍两者的区别和索引建立在json列和字段上的不同作用。
btree索引
btree的索引需要区分json的字段,对列建立的索引不会再按照字段查询的时候生效。也就是建立的索引需要类型严格匹配,路径也需要匹配才能走索引。
对列建立的索引
create index library_params_btree on library using btree(params);
explain analyze select * from library where params @> '{"catalog":[{"name": "TldvTAsvuM"}]}';
查询结构如下没有走索引
 Gather  (cost=1000.00..44061.33 rows=100 width=275) (actual time=402.955..405.912 rows=1 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Seq Scan on library  (cost=0.00..43051.33 rows=42 width=275) (actual time=392.910..392.977 rows=0 loops=3)
 "        Filter: (params @> '{""catalog"": [{""name"": ""TldvTAsvuM""}]}'::jsonb)"
         Rows Removed by Filter: 333333
 Planning Time: 0.276 ms
 Execution Time: 405.927 ms
采用整个列的内容进行查询,使用了索引
 explain analyze select * from library where params = '{"catalog": [{"name": "TldvTAsvuM"}, {"name": "UOLFmReEXC"}]}';

 Index Scan using library_params_btree on library  (cost=0.68..8.69 rows=1 width=275) (actual time=0.079..0.080 rows=1 loops=1)
 "  Index Cond: (params = '{""catalog"": [{""name"": ""TldvTAsvuM""}, {""name"": ""UOLFmReEXC""}]}'::jsonb)"
 Planning Time: 0.061 ms
 Execution Time: 0.093 ms
对字段进行索引
 create index library_params_btree_params_catalog on library using btree((params->>'catalog'));

explain analyze select * from library where params ->> 'catalog' = '[{"name": "TldvTAsvuM"}, {"name": "UOLFmReEXC"}]';

 Bitmap Heap Scan on library  (cost=619.42..14503.62 rows=5000 width=275) (actual time=0.027..0.028 rows=1 loops=1)
 "  Recheck Cond: ((params ->> 'catalog'::text) = '[{""name"": ""TldvTAsvuM""}, {""name"": ""UOLFmReEXC""}]'::text)"
   Heap Blocks: exact=1
   ->  Bitmap Index Scan on library_params_btree_params_catalog  (cost=0.00..618.17 rows=5000 width=0) (actual time=0.024..0.024 rows=1 loops=1)
 "        Index Cond: ((params ->> 'catalog'::text) = '[{""name"": ""TldvTAsvuM""}, {""name"": ""UOLFmReEXC""}]'::text)"
 Planning Time: 0.063 ms
 Execution Time: 0.044 ms
使用@>操作符也不会走索引
 explain analyze select * from library where params @> '{"catalog":[{"name": "KftNvvdQgn"}, {"name": "kcNwEkPmJy"}]}';
gin索引
gin索引是一种倒排索引(Generalized Inverted Index),gin索引常用于json、jsonb和全文搜索的常见。倒排索引的查询速度很快,但是写入和修改速度比较慢。文章比较好的解释了PostgreSQL中的GIN索引,具体的内容需要读源码来了解浅谈postgresql的GIN索引(通用倒排索引)
Document 1: The quick brown fox jumped over the lazy dog. Document 2: The lazy dog slept in the sun.
 The    -> Document 1, Document 2
 Quick  -> Document 1
 Brown  -> Document 1
 Fox    -> Document 1
 Jumped -> Document 1
 Over   -> Document 1
 Lazy   -> Document 1, Document 2
 Dog    -> Document 1, Document 2
 Slept  -> Document 2
 In     -> Document 2
 Sun    -> Document 2

json、jsonb推荐gin索引的原因不只是搜索的方便,还以为索引自由模式。


索引自由模式
索引自由模式是指索引时不需要指定JSON数据中的指定字段,后续就可以按JSON中已的任意的字段进行查询。在其他的数据库MySQL和MongoDB上面都没有实现索引自由模式,而在PostgreSQL中通过GIN索引实现了。下面的索引建立的例子里可以看到效果,在示例数据库中除了主键索引只建立下面的索引:
 create index library_params_gin on library using gin(params);
运行下面两条查询语句,都进行了索引搜索:
?是直接进行jsonb的比较操作,@>操作符是包含后续的json
 explain analyze select * from library where params ? '{"catalog":[{"name": "KftNvvdQgn"}, {"name": "kcNwEkPmJy"}]}';
 explain analyze select * from library where params @> '{"catalog": [{"name": "TldvTAsvuM"}, {"name": "UOLFmReEXC"}]}';
gin索引类型
gin索引有两种类型可以选择JSONB_OPS和JSONB_PATH_OPS。默认的类型是JSON_OPS。
JSONB_OPS
操作符
JSONB_OPS类型创建的索引在@>、?、?&、?|会进行索引查询
explain analyze select * from library where params @> '{"catalog":[{"name": "KftNvvdQgn"}, {"name": "kcNwEkPmJy"}]}';
例如使用->来查询:并不会走索引
 explain analyze select * from library where params -> 'catalog'?'[{"name": "KftNvvdQgn"}, {"name": "kcNwEkPmJy"}]';

Gather  (cost=1000.00..46093.00 rows=10000 width=275) (actual time=85.055..87.858 rows=0 loops=1)
   Workers Planned: 2
   Workers Launched: 2
   ->  Parallel Seq Scan on library  (cost=0.00..44093.00 rows=4167 width=275) (actual time=74.086..74.086 rows=0 loops=3)
 "        Filter: ((params -> 'catalog'::text) ? '[{""name"": ""KftNvvdQgn""}, {""name"": ""kcNwEkPmJy""}]'::text)"
         Rows Removed by Filter: 333333
 Planning Time: 0.099 ms
 Execution Time: 87.874 ms
如果需要对params->'catalog'的查询使用索引,在索引建立的时候要使用params->'catalog'
 create index library_params_gin_params_catalog on library using gin((params->'catalog'));

explain analyze select * from library where params -> 'catalog'?'[{"name": "KftNvvdQgn"}, {"name": "kcNwEkPmJy"}]';

 Bitmap Heap Scan on library  (cost=113.50..22793.12 rows=10000 width=275) (actual time=0.016..0.016 rows=0 loops=1)
 "  Recheck Cond: ((params -> 'catalog'::text) ? '[{""name"": ""KftNvvdQgn""}, {""name"": ""kcNwEkPmJy""}]'::text)"
   ->  Bitmap Index Scan on library_params_gin_params_catalog  (cost=0.00..111.00 rows=10000 width=0) (actual time=0.014..0.014 rows=0 loops=1)
 "        Index Cond: ((params -> 'catalog'::text) ? '[{""name"": ""KftNvvdQgn""}, {""name"": ""kcNwEkPmJy""}]'::text)"
 Planning Time: 0.193 ms
 Execution Time: 0.033 ms
而使用=进行比较的话也不会走索引
 
explain analyze select * from library where params -> 'catalog'='[{"name": "KftNvvdQgn"}, {"name": "kcNwEkPmJy"}]';
也可以使用@?
explain analyze select * from library where params @? ('($.catalog[*] ?(@.name == "KftNvvdQgn"))');
但是如果在json path上使用json path处理函数的话,就无法使用索引了
 explain analyze select * from library where params @? ('($.catalog[*].size() == "2")');

Seq Scan on library  (cost=0.00..50343.00 rows=999900 width=275) (actual time=0.010..949.993 rows=1000000 loops=1)
 "  Filter: (params @? '($.""catalog""[*].size() == ""2"")'::jsonpath)"
 Planning Time: 0.181 ms
 Execution Time: 976.060 ms
JSONB_PATH_OPS
JSONB_PATH_OPS索引只能支持@>和@?操作符
create index library_params_gin_params_catalog_jsonb_path_ops on library using gin(params JSONB_PATH_OPS);
下面两个语句不会使用索引
 explain analyze select * from library where params ? '{"catalog":[{"name": "KftNvvdQgn"}, {"name": "kcNwEkPmJy"}]}';
 explain analyze select * from library where params ?| '{"catalog"}';
下面两个语句会使用索引
explain analyze select * from library where params @> '{"catalog":[{"name": "KftNvvdQgn"}, {"name": "kcNwEkPmJy"}]}';
 explain analyze select * from library where params @? ('($.catalog[*] ?(@.name == "KftNvvdQgn"))');
JSONB_OPS和JSONB_PATH_OPS的区别

尽管jsonb_path_ops操作符类只支持用 @>,@@和@?操作符的查询,但它比起默认的操作符类 jsonb_ops有更客观的性能优势。一个 jsonb_path_ops索引通常也比一个相同数据上的 jsonb_ops要小得多,并且搜索的专一性更好,特 别是当查询包含频繁出现在该数据中的键时。因此,其上的搜索操作 通常比使用默认操作符类的搜索表现更好。


jsonb_ops和jsonb_path_ops GIN 索引之间的技术区别是前者为数据中的每一个键和值创建独立的索引项, 而后者值为该数据中的每个值创建索引项。 基本上,每一个jsonb_path_ops索引项是其所对应的值和 键的哈希。例如要索引{"foo": {"bar": "baz"}},将创建一个 单一的索引项,它把所有三个foo、bar、 和baz合并到哈希值中。因此一个查找这个结构的包含查询可能 导致极度详细的索引搜索。但是根本没有办法找到foo是否作为 一个键出现。在另一方面,一个jsonb_ops会创建三个索引 项分别表示foo、bar和baz。那么要 做同样的包含查询,它将会查找包含所有三个项的行。虽然 GIN 索引能够相当 有效地执行这种 AND 搜索,它仍然不如等效的 jsonb_path_ops搜索那样详细和快速(特别是如果有大量 行包含三个索引项中的任意一个时)。


jsonb_path_ops方法的一个不足是它不会为不包含任何值 的 JSON 结构创建索引项,例如{"a": {}}。如果需要搜索包 含这样一种结构的文档,它将要求一次全索引扫描,那就非常慢。 因此jsonb_path_ops不适合经常执行这类搜索的应用。

示例数据库
 create table library(id bigserial primary key ,
     params jsonb,
     delete_status bool
 );
 create index library_params_gin on library using gin(params);
params格式,其中数组长度{2-12}随机
 {"catalog": [{"name": "KftNvvdQgn"}, {"name": "kcNwEkPmJy"}]}
使用navicat生成数据,DML语句后续给出。
用户评论