六月 3, 2024
摘要:在本教程中,您将学习如何在 PostgreSQL 中为 JSONB 类型的列创建 JSON 索引,以提高查询性能。
目录
PostgreSQL JSON 索引简介
JSONB(二进制 JSON)是一种数据类型,允许您存储 JSON 数据并对其进行高效查询。
当一个 JSONB 列具有复杂的 JSON 结构时,使用索引可以显著提高查询性能。
PostgreSQL 使用GIN
索引类型来索引具有 JSONB 数据类型的列。GIN
的意思是通用倒排索引。
请注意,您可以将 GIN 索引用于 tsvector 或数组列。
要为 JSONB 列创建GIN
索引,可以使用下面的CREATE INDEX
语句:
CREATE INDEX index_name
ON table_name
USING GIN(jsonb_column);
此语句在jsonb_column
上创建GIN
索引。这种GIN
索引适用于对 JSONB 数据的通用查询。
在一个 JSONB 列上创建GIN
索引时,可以使用特定的GIN
操作符类。
操作符类确定 PostgreSQL 如何构建索引,以及如何优化索引列上的查询。
例如,下面的CREATE INDEX
语句,使用jsonb_path_ops
操作符类在jsonb_coumn
上创建GIN
索引:
CREATE INDEX index_name
ON table_name
USING GIN(jsonb_column jsonb_path_ops);
该索引针对使用 @> (contains)、? (exists) 和 @@ 的 JSONB 运算符的查询做了优化。它对于涉及 JSONB 文档中的键值搜索非常有用。
下表显示了GIN
操作符类:
名称 | 可索引的操作符 |
---|---|
array_ops |
&& (anyarray,anyarray) |
@> (anyarray,anyarray) |
|
<@ (anyarray,anyarray) |
|
= (anyarray,anyarray) |
|
jsonb_ops |
@> (jsonb,jsonb) |
@? (jsonb,jsonpath) |
|
@@ (jsonb,jsonpath) |
|
? (jsonb,text) |
|
?| (jsonb,text[]) |
|
?& (jsonb,text[]) |
|
jsonb_path_ops |
@> (jsonb,jsonb) |
@? (jsonb,jsonpath) |
|
@@ (jsonb,jsonpath) |
|
tsvector_ops |
@@ (tsvector,tsquery) |
@@@ (tsvector,tsquery) |
请注意,如果您没有显式指定GIN
操作符类,则该语句将默认使用jsonb_ops
操作符,这适用于大多数情况。
此外,PostgreSQL 允许您为 JSON 文档中的特定字段创建GIN
索引,如下所示:
CREATE INDEX index_name
ON table_name
USING GIN ((data->'field_name') jsonb_path_ops);
如果有一个 JSON 文档存储在 JSONB 列中,当查询涉及在该 JSON 文档数据的字段field_name
中搜索值时,该索引可以改进查询性能。
PostgreSQL JSON 索引示例
我们将使用示例数据库中的表。
1) 设置一个示例表
首先,创建一个新表,名为customer_json
,以 JSON 格式存储客户信息:
CREATE TABLE customer_json(
id SERIAL PRIMARY KEY,
data JSONB NOT NULL
);
第二步,将customer
、address
、city
和country
表中的数据插入到customer_json
表中:
WITH json_cte AS(
SELECT
jsonb_build_object(
'first_name', first_name,
'last_name', last_name,
'email', email,
'phone', a.phone,
'address',
jsonb_build_object(
'address', a.address,
'city', i.city,
'postal_code', a.postal_code,
'district', a.district,
'country', o.country
)
):: jsonb AS data
FROM
customer c
INNER JOIN address a ON a.address_id = c.address_id
INNER JOIN city i ON i.city_id = a.city_id
INNER JOIN country o ON o.country_id = i.country_id
)
INSERT INTO customer_json(data)
SELECT data FROM json_cte;
第三步,检索名为John
的客户的电子邮件:
SELECT
data ->> 'first_name' first_name,
data ->> 'last_name' last_name,
data ->> 'phone' phone
FROM
customer_json
WHERE
data @> '{"first_name": "John"}';
输出:
first_name | last_name | phone
------------+------------+-------------
John | Farnsworth | 51917807050
(1 row)
最后,对上面的查询进行解释和分析:
EXPLAIN ANALYZE
SELECT
data ->> 'first_name' first_name,
data ->> 'last_name' last_name,
data ->> 'phone' phone
FROM
customer_json
WHERE
data @> '{"first_name": "John"}';
输出:
QUERY PLAN
---------------------------------------------------------------------------------------------------------
Seq Scan on customer_json (cost=0.00..31.50 rows=1 width=96) (actual time=0.063..0.118 rows=1 loops=1)
Filter: (data @> '{"first_name": "John"}'::jsonb)
Rows Removed by Filter: 598
Planning Time: 1.109 ms
Execution Time: 0.128 ms
(5 rows)
输出表明 PostgreSQL 必须扫描整个customer_json
表来搜索客户。
要提升查询的性能,可以在customer_json
表的数据列上创建GIN
索引。
2) 在 JSONB 列上创建索引
首先,在customer_json
表的data
列上创建一个索引:
CREATE INDEX customer_json_index
ON customer_json
USING GIN(data);
第二步,执行查询,搜索名为John
的客户:
EXPLAIN ANALYZE
SELECT
data ->> 'first_name' first_name,
data ->> 'last_name' last_name,
data ->> 'phone' phone
FROM
customer_json
WHERE
data @> '{"first_name": "John"}';
输出:
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on customer_json (cost=21.51..25.53 rows=1 width=96) (actual time=0.024..0.024 rows=1 loops=1)
Recheck Cond: (data @> '{"first_name": "John"}'::jsonb)
Heap Blocks: exact=1
-> Bitmap Index Scan on customer_json_index (cost=0.00..21.51 rows=1 width=0) (actual time=0.014..0.014 rows=1 loops=1)
Index Cond: (data @> '{"first_name": "John"}'::jsonb)
Planning Time: 0.164 ms
Execution Time: 0.045 ms
(7 rows)
查询计划表明 PostgreSQL 使用了customer_json_index
来提升性能。
这一次,执行时间明显缩短了,0.045ms
对比0.128ms
,比不使用GIN
索引的查询快了 2 - 3 倍。
3) 使用 GIN 操作符类在 JSONB 列上创建索引
首先,删除索引customer_json_index
:
DROP INDEX customer_json_index;
第二步,使用GIN
操作符类,在customer_json
表的 data 列上创建GIN
索引:
CREATE INDEX customer_json_index
ON customer_json
USING GIN(data jsonb_path_ops);
第三步,解释查询,该查询查找名为John
的客户:
EXPLAIN ANALYZE
SELECT
data ->> 'first_name' first_name,
data ->> 'last_name' last_name,
data ->> 'phone' phone
FROM
customer_json
WHERE
data @> '{"first_name": "John"}';
输出:
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on customer_json (cost=12.82..16.84 rows=1 width=96) (actual time=0.014..0.015 rows=1 loops=1)
Recheck Cond: (data @> '{"first_name": "John"}'::jsonb)
Heap Blocks: exact=1
-> Bitmap Index Scan on customer_json_index (cost=0.00..12.82 rows=1 width=0) (actual time=0.008..0.008 rows=1 loops=1)
Index Cond: (data @> '{"first_name": "John"}'::jsonb)
Planning Time: 0.120 ms
Execution Time: 0.034 ms
(7 rows)
查询计划显示查询确实使用了customer_json_index
来提升性能。
最后,解释查询,该查询搜索 data 列中first_name
字段中的值为 John 的客户:
EXPLAIN ANALYZE
SELECT * FROM customer_json
WHERE data->>'first_name' = 'John';
输出:
QUERY PLAN
----------------------------------------------------------------------------------------------------------
Seq Scan on customer_json (cost=0.00..32.98 rows=3 width=275) (actual time=0.161..0.284 rows=1 loops=1)
Filter: ((data ->> 'first_name'::text) = 'John'::text)
Rows Removed by Filter: 598
Planning Time: 0.085 ms
Execution Time: 0.298 ms
(5 rows)
在此计划中,查询无法充分利用GIN
索引customer_json_index
。原因是查询没有使用 JSONB 操作符(@
、@?
、@@
),而jsonb_path_ops
操作符类是针对这些操作符进行优化的。
4) 在 JSONB 列的特定字段上创建索引
首先,删除索引customer_json_index
:
DROP INDEX customer_json_index;
第二步,使用GIN
操作符类,在customer_json
表的first_name
字段上创建GIN
索引:
CREATE INDEX customer_json_index
ON customer_json
USING GIN((data->'first_name'));
第三步,解释查询,该查询查找 JSONB 列data
中的 “first_name
“ 字段包含值"John"
的行:
EXPLAIN ANALYZE
SELECT
data ->> 'first_name' first_name,
data ->> 'last_name' last_name,
data ->> 'phone' phone
FROM
customer_json
WHERE
data->'first_name' @> '"John"';
输出:
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Bitmap Heap Scan on customer_json (cost=8.58..23.72 rows=6 width=96) (actual time=0.031..0.032 rows=1 loops=1)
Recheck Cond: ((data -> 'first_name'::text) @> '"John"'::jsonb)
Heap Blocks: exact=1
-> Bitmap Index Scan on customer_json_index (cost=0.00..8.58 rows=6 width=0) (actual time=0.015..0.015 rows=1 loops=1)
Index Cond: ((data -> 'first_name'::text) @> '"John"'::jsonb)
Planning Time: 0.167 ms
Execution Time: 0.133 ms
(7 rows)
输出表明查询使用了customer_json_index
索引。
总结
使用GIN
索引为表的 JSONB 列创建索引,以提升查询性能。