本文是《PostgreSQL实战》的读书笔记,感兴趣的可以参看本书对应章节。一、PostgreSQL的NoSQL特性PostgreSQL不只是一个关系型数据库
本文是《PostgreSQL实战》的读书笔记,感兴趣的可以参看本书对应章节。
一、PostgreSQL的NoSQL特性
PostgreSQL不只是一个关系型数据库,同时支持非关系特性,而且逐步增加对非关系特性的支持。
1.1 为jsonb类型创建索引
jsonb数据类型支持GIN
索引。
{"id":1,"user_id":122,"user_name":"1_joker","create_time":"2018-11-25 20:48:10"
}
表tbl_user_jsonb
中的user_info
字段类型是jsonb 。在user_info创建GIN索引。
create index idx_gin ON tbl_user_jsonb USING gin(user_info);
jsonb上的GIN支持 @>
、?
、 ?&
、?|
操作符。例如
select * from tbl_user_jsonb where user_info @> '{"user_name":"1_joker"}'
select * from tbl_user_jsonb where user_info ->>'user_name' = '1_joker';
create index idx_gin_user_infob_user_name ON tbl_user_jsonb Using btree(user_info->>'user_name')
1.2 json、jsonb读写性能测试
json写入时比jsonb快,但检索时比jsonb慢。原因:json存储格式为文本而jsonb为二进制,存储格式的不同使的两种json数据类型的处理效率不一样。json类型存储的内容和输入数据一样,当检索json数据时必须重新解析;而jsonb以二进制存储已解析好的数据,当检索jsonb数据时不需要重新解析。
通过简答的例子测试json、jsonb的读写性能差异。
create table user_ini(id int4,user_id int8,user_name character varying(64),create_time timestamp(6) with time zone default clock_timestamp()
);
INSERT INTO user_ini(id,user_id,user_name)
SELECT r,round(random()*2000000),r||'_frances'
FROM generate_series(1,2000000) AS r;
CREATE TABLE tbl_user_json(id serial,user_info json);CREATE TABLE tbl_user_jsonb(id serial,user_info jsonb);
json、 jsonb 表写性能测试
根据user_ini
数据通过 row_to_json
函数向表user_ini_json
插入200万json数据。
INSERT INTO tbl_user_json(user_info) SELECT row_to_json(user_ini) FROM user_ini;
INSERT INTO tbl_user_jsonb(user_info) SELECT row_to_json(user_ini) FROM user_ini;
可以明显看出来,json的写性能要比jsonb好 。从占用空间来看,同样数量jsonb数据类型占用空间比json稍大。
json、jsonb表读性能测试
根据user_info
字段的user_name
键的值查询
注意:关闭并行查询 SET max_parallel_workers_per_gather = 0
EXPLAIN ANALYSE SELECT * FROM tbl_user_jsonb WHERE user_info ->>'user_name' = '1_frances';
Seq Scan on tbl_user_jsonb (cost=0.00..73500.32 rows=10000 width=144) (actual time=0.800..591.076 rows=1 loops=1)Filter: ((user_info ->> 'user_name'::text) = '1_frances'::text)Rows Removed by Filter: 1999999
Planning time: 0.056 ms
Execution time: 591.101 ms
基于user_info
字段的user_name
键值创建btree索引
CREATE INDEX idx_jsonb ON tbl_user_jsonb USING btree((user_info->>'user_name'));
再次执行上述查询:
EXPLAIN ANALYSE SELECT * FROM tbl_user_jsonb WHERE user_info ->>'user_name' = '1_frances';
Bitmap Heap Scan on tbl_user_jsonb (cost=233.93..24043.95 rows=10000 width=144) (actual time=0.372..0.373 rows=1 loops=1)Recheck Cond: ((user_info ->> 'user_name'::text) = '1_frances'::text)Heap Blocks: exact=1-> Bitmap Index Scan on idx_jsonb (cost=0.00..231.43 rows=10000 width=0) (actual time=0.328..0.328 rows=1 loops=1)Index Cond: ((user_info ->> 'user_name'::text) = '1_frances'::text)
Planning time: 0.629 ms
Execution time: 0.429 ms
通过比较执行计划可以看出走了索引,并且SQL时间下降。
为了更好地对比 tbl_user_json、tbl_user_jsonb 表基于键值查询的效率,对user_info字段id键进行范围扫描以对比性能。
CREATE INDEX idx_gin_user_info_id ON tbl_user_json USING btree(((user_info ->>'id')::integer));CREATE INDEX idx_gin_user_infob_id ON tbl_user_jsonb USING btree(((user_info ->>'id')::integer));
EXPLAIN ANALYSE SELECT id,user_info->'id',user_info->'user_name' FROM tbl_user_json
WHERE(user_info->>&#39;id&#39;)::int4 > 1 AND (user_info->>&#39;id&#39;)::int4 < 10000;
Bitmap Heap Scan on tbl_user_json (cost&#61;214.93..22741.47 rows&#61;10000 width&#61;68) (actual time&#61;1.830..41.511 rows&#61;9998 loops&#61;1)Recheck Cond: ((((user_info ->> &#39;id&#39;::text))::integer > 1) AND (((user_info ->> &#39;id&#39;::text))::integer < 10000))Heap Blocks: exact&#61;173-> Bitmap Index Scan on idx_gin_user_info_id (cost&#61;0.00..212.43 rows&#61;10000 width&#61;0) (actual time&#61;1.762..1.762 rows&#61;9998 loops&#61;1)Index Cond: ((((user_info ->> &#39;id&#39;::text))::integer > 1) AND (((user_info ->> &#39;id&#39;::text))::integer < 10000))
Planning time: 0.389 ms
Execution time: 42.467 ms
查询表tbl_user_json的user_info字段id键值在1到10000范围内走了索引。
EXPLAIN ANALYSE SELECT id,user_info->&#39;id&#39;,user_info->&#39;user_name&#39; FROM tbl_user_jsonb
WHERE(user_info->>&#39;id&#39;)::int4 > 1 AND (user_info->>&#39;id&#39;)::int4 < 10000;
Bitmap Heap Scan on tbl_user_jsonb (cost&#61;214.93..24224.95 rows&#61;10000 width&#61;68) (actual time&#61;1.269..12.417 rows&#61;9998 loops&#61;1)Recheck Cond: ((((user_info ->> &#39;id&#39;::text))::integer > 1) AND (((user_info ->> &#39;id&#39;::text))::integer < 10000))Heap Blocks: exact&#61;213-> Bitmap Index Scan on idx_gin_user_infob_id (cost&#61;0.00..212.43 rows&#61;10000 width&#61;0) (actual time&#61;1.175..1.175 rows&#61;9998 loops&#61;1)Index Cond: ((((user_info ->> &#39;id&#39;::text))::integer > 1) AND (((user_info ->> &#39;id&#39;::text))::integer < 10000))
Planning time: 0.271 ms
Execution time: 13.074 ms
执行时间明显变短了。
从上面的测试看出&#xff0c;json写入比jsonb快&#xff0c;但检索时比jsonb慢。
二、全文索引对json和jsonb数据类型的支持
对于多数应用来说全文检索很少在数据库中实现&#xff0c;一般使用单独的全文检索引擎&#xff0c;例如基于SQL的全文检索引擎Sphinx。PostgreSQL支持全文检索&#xff0c;对于规模不大的应用如果不想搭建专门的搜索引擎&#xff0c;PostgreSQL的全文检索也可满足需求。
大部分检索需要通过数据库like操作匹配&#xff0c;这种检索方式的主要缺点&#xff1a;
- 不能很好地支持索引&#xff0c;通常需要全表扫描检索数据&#xff0c;数据量大时检索性能很低。
- 不能提供检索结果排序&#xff0c;当输出结果数据量非常大时表现更加明显
PostgreSQL全文检索有效地解决这个问题。PostgreSQL全文检索通过以下两种数据类型来实现。
tsvector
tsvector 全文检索数据类型代表一个被优化的可以基于搜索的文档&#xff0c;要将一串字符串转换成tsvector全文检索数据类型。
SELECT &#39;Hello,cat,how are u? cat is smiling!&#39;::tsvector;
SELECT to_tsvector(&#39;english&#39; ,&#39;Hello cat,&#39;)
tsquery
tsquery 表示一个文本查询&#xff0c;存储用于搜索的词&#xff0c;并且支持布尔操作 &
、|
、!
SELECT &#39;hello&cat&#39;::tsquery;
SELECT to_tsquery(&#39;hello&cat&#39;);
SELECT to_tsvector(&#39;english&#39;,&#39;Hello cat, how are u&#39;) &#64;&#64; to_tsquery(&#39;hello&cat&#39;);
SELECT to_tsvector(&#39;english&#39;,&#39;Hello cat, how are u&#39;) &#64;&#64; to_tsquery(&#39;hello&dog&#39;);
英文全文检索例子
在PostgreSQL10之前全文检索不支持json和jsonb数据类型。10版本则开始支持这两种类型。