PostgreSQL JSON 类型详解

简介

JSON 代表 JavaScript Object Notation。它是一种开放标准格式,将数据组织成 RFC 7159 中详述的键/值对和数组

为什么要在PostgreSQL中存储JSON

  1. 架构灵活性

    使用 JSON 格式存储数据的主要原因之一是架构灵活性。当架构不稳定且频繁更改时,将数据存储在 JSON 中非常有用。如果将每个键存储为列,则会导致频繁的 DML 操作 - 当您的数据集很大时,这可能很困难 - 例如,事件跟踪、分析、标签等。注意:如果文档中始终存在特定键,则将其存储为第一类列可能是有意义的。我们将在下面的“JSON 模式和反模式”部分中讨论有关此方法的更多信息。

  2. 嵌套对象

    如果您的数据集具有嵌套对象(单级或多级),在某些情况下,在 JSON 中处理它们比将数据非规范化为列或多个表更容易。

  3. 与外部数据源同步

    通常,外部系统以 JSON 形式提供数据,因此在将数据摄取到系统的其他部分之前,它可能是临时存储。例如,条纹交易。

PostgreSQL 中对 JSON 支持的时间点

1. PostgreSQL 9.2 (2012) 添加了对 JSON 数据类型的支持

2. PostgreSQL 9.4 (2014) 添加了对 JSONB 数据类型的支持

JSONB 支持为 JSON 数据编制索引,并且在解析和查询 JSON 数据方面非常高效。在大多数情况下,当你在 PostgreSQL 中使用 JSON 时,你应该使用 JSONB。

3. PostgreSQL 12(2019 年)增加了对 SQL/JSON 标准和 JSONPATH 查询的支持

JSONPath 为 PostgreSQL 带来了强大的 JSON 查询引擎。

什么时候应该使用 JSON 而不是 JSONB?

在大多数情况下,JSONB 是您应该使用的。但是,在某些特定情况下,JSON 效果更好:

  • JSON 保留原始格式(也称为空格)和键的顺序。
  • JSON 保留重复的键。
  • 与 JSONB 相比,JSON 的摄取速度更快 - 但是,如果您进行任何进一步的处理,JSONB 将更快。
    例如,如果您只是摄取 JSON 日志而不以任何方式查询它们,那么 JSON 可能是更好的选择

JSONB 运算符和函数

PostgreSQL提供了各种运算符来处理JSONB。从文档中:

算子 描述
-> 获取 JSON 数组元素(从零开始索引,从末尾开始计数负整数)
-> 按键获取 JSON 对象字段
->> 以文本形式获取 JSON 数组元素
->> 以文本形式获取 JSON 对象字段
#> 获取指定路径中的 JSON 对象
#>> 以文本形式获取指定路径处的 JSON 对象
@> 左侧 JSON 值是否在顶层包含正确的 JSON 路径/值条目?
<@ 左侧 JSON 路径/值条目是否包含在右侧 JSON 值中的顶层?
? _字符串_是否作为 JSON 值中的顶级键存在?
?| 这些数组_字符串_中的任何一个是否作为顶级键存在?
?& 所有这些数组_字符串_是否都作为顶级键存在?
| 将两个 jsonb 值连接成一个新的 jsonb 值
从左侧操作数中删除键/值对或_字符串_元素。键/值对根据其键值进行匹配。
从左侧操作数中删除多个键/值对或_字符串_元素。键/值对根据其键值进行匹配。
删除具有指定索引的数组元素(从末尾开始计算负整数)。如果顶级容器不是数组,则引发错误。
#- 删除具有指定路径的字段或元素(对于 JSON 数组,负整数从末尾开始计数)
@? JSON 路径是否返回指定 JSON 值的任何项目?
@@ 返回指定 JSON 值的 JSON 路径谓词检查结果。仅考虑结果的第一项。如果结果不是布尔值,则返回 null。

JSONB 相关的索引

我们主要讨论 GIN;BTREE 与 HASH

GIN 索引

支持两种操作类型

  • jsonb_ops (default) [索引 JSONB 中的每个键与值]

    ?,?|,?&,@>,@@,@?

  • jsonb_pathops [只是索引 JSONB 中 的值]]

    @>,@?

实例

准备数据

drop table if exists  test cascade;
CREATE TABLE test(id bigserial, data JSONB, PRIMARY KEY (id));
CREATE INDEX idx_test_data ON test USING gin (data);

insert into test(data) values('{"name":"lxm","age":10,"nick_name":["xiaoming","baobao"],"phone_list":["1111","2222"]}'::jsonb);

查询 顶层 关键词是否存在(可以使用到 gin 索引)

set enable_seqscan = off;
select * from test where data ? 'id';  -- 查询一个关键词
explain (verbose, analyse, costs, buffers) select * from test where data ? 'id';  

select * from test where data ?| array['id','name']; --查询多个关键词
explain (verbose, buffers) select * from test where data ?| array['id','name']; --查询多个关键词


lxm=# set enable_seqscan = off;
SET
lxm=# select * from test where data ? 'id';  -- 查询一个关键词
 id | data
----+------
(0 rows)

lxm=# explain (verbose,analyse,costs,buffers) select * from test where data ? 'id';
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.test  (cost=12.09..22.78 rows=12 width=40) (actual time=0.004..0.004 rows=0 loops=1)
   Output: id, data
   Recheck Cond: (test.data ? 'id'::text)
   Buffers: shared hit=3
   ->  Bitmap Index Scan on idx_test_data  (cost=0.00..12.09 rows=12 width=0) (actual time=0.003..0.003 rows=0 loops=1)
         Index Cond: (test.data ? 'id'::text)
         Buffers: shared hit=3
 Planning:
   Buffers: shared hit=1
 Planning Time: 0.020 ms
 Execution Time: 0.014 ms
(11 rows)

lxm=#
lxm=# select * from test where data ?| array['id','name']; --查询多个关键词
 id |                                              data
----+-------------------------------------------------------------------------------------------------
  1 | {"age": 10, "name": "lxm", "nick_name": ["xiaoming", "baobao"], "phone_list": ["1111", "2222"]}
(1 row)

lxm=# explain (verbose,buffers) select * from test where data ?| array['id','name']; --查询多个关键词
                                                       QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.test  (cost=16.09..26.78 rows=12 width=40) (actual time=0.017..0.018 rows=1 loops=1)
   Output: id, data
   Recheck Cond: (test.data ?| '{id,name}'::text[])
   Heap Blocks: exact=1
   Buffers: shared hit=5
   ->  Bitmap Index Scan on idx_test_data  (cost=0.00..16.09 rows=12 width=0) (actual time=0.010..0.011 rows=1 loops=1)
         Index Cond: (test.data ?| '{id,name}'::text[])
         Buffers: shared hit=4
 Planning:
   Buffers: shared hit=1
 Planning Time: 0.082 ms
 Execution Time: 0.034 ms
(12 rows)

查询 非顶层 关键词是否存在(无法使用到 gin 索引)

set enable_seqscan = off;
select * from test where data->'name' ? 'lxm';  
explain (verbose, buffers) select * from test where data->'name' ? 'lxm';  

lxm=# set enable_seqscan = off;
SET
lxm=# select * from test where data->'name' ? 'lxm';  
 id |                                              data
----+-------------------------------------------------------------------------------------------------
  1 | {"age": 10,buffers) select * from test where data->'name' ? 'lxm';  
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Seq Scan on public.test  (cost=10000000000.00..10000000028.00 rows=12 width=40) (actual time=0.021..0.023 rows=1 loops=1)
   Output: id, data
   Filter: ((test.data -> 'name'::text) ? 'lxm'::text)
   Buffers: shared hit=1
 Planning Time: 0.097 ms
 Execution Time: 0.043 ms
(6 rows)

可以看到无法使用到索引,那如何解决,可以使用下面 方法

非顶层关键词 使用索引的 方法

-- 千万注意 gin 后面有两个括号,用单个括号会语法报错
drop index if exists idx_test_data_nick_name;
create index idx_test_data_nick_name on test using gin((data->'nick_name'));  
set enable_seqscan = off;
select * from test where data->'nick_name' ? 'xiaoming';  
explain (verbose, buffers) select * from test where data->'nick_name' ? 'xiaoming';  

lxm=# drop index if exists idx_test_data_nick_name;
DROP INDEX
lxm=# create index idx_test_data_nick_name on test using gin((data->'nick_name'));
CREATE INDEX
lxm=# set enable_seqscan = off;
SET
lxm=# select * from test where data->'nick_name' ? 'xiaoming';
 id |                                              data
----+-------------------------------------------------------------------------------------------------
  1 | {"age": 10,buffers) select * from test where data->'nick_name' ? 'xiaoming';
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on public.test  (cost=8.00..12.02 rows=1 width=40) (actual time=0.018..0.019 rows=1 loops=1)
   Output: id, data
   Recheck Cond: ((test.data -> 'nick_name'::text) ? 'xiaoming'::text)
   Heap Blocks: exact=1
   Buffers: shared hit=3
   ->  Bitmap Index Scan on idx_test_data_name  (cost=0.00..8.00 rows=1 width=0) (actual time=0.009..0.010 rows=1 loops=1)
         Index Cond: ((test.data -> 'nick_name'::text) ? 'xiaoming'::text)
         Buffers: shared hit=2
 Planning:
   Buffers: shared hit=1
 Planning Time: 0.080 ms
 Execution Time: 0.040 ms
(12 rows)

@> 的使用:表示是否包含子json对象

lxm=# select * from test;
 id |                                              data
----+-------------------------------------------------------------------------------------------------
  1 | {"age": 10, "2222"]}
(1 row)

lxm=# select * from test where data @> '{"age":10}';
 id |                                              data
----+-------------------------------------------------------------------------------------------------
  1 | {"age": 10, "2222"]}
(1 row)

lxm=#
lxm=# select * from test where data @> '{"age":11}';
 id | data
----+------
(0 rows)

lxm=# select * from test where data @> '{"age":10,"nick_name":["xiaoming"]}';
 id |                                              data
----+-------------------------------------------------------------------------------------------------
  1 | {"age": 10, "2222"]}
(1 row)

lxm=#
lxm=# select * from test where data @> '{"age":10,"nick_name":["xiaomi"]}';
 id | data
----+------
(0 rows)

path_ops 支持

GIN 还支持“pathops”选项来减小 GIN 索引的大小。使用 pathops 选项时,只支持 @> 这一个运算符

文档中:

jsonb_ops : 数据中的每个键和值创建独立的索引项

jsonb_path_ops :只给数据中的每值创建独立的索引项

BTREE 索引

B 树索引是关系数据库中最常见的索引类型。但是,如果使用 B 树索引索引整个 JSONB 列,则唯一有用的运算符是“=”、<、<=、>、>=。从本质上讲,这只能用于整个对象比较,其用例非常有限。

普通查询

  • 代码

    set enable_seqscan = 0;
    select * from test where (data->>'age')::int>1;
    
    explain (verbose, buffers) select * from test where (data->>'age')::int>1;
    
  • 演示

lxm=#
lxm=# set enable_seqscan = 0;
SET
lxm=# select * from test where (data->>'age')::int>1;
 id |                                              data
----+-------------------------------------------------------------------------------------------------
  1 | {"age": 10, "2222"]}
(1 row)

lxm=#
lxm=# explain (verbose,buffers) select * from test where (data->>'age')::int>1;
                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Seq Scan on public.test  (cost=10000000000.00..10000000001.02 rows=1 width=40) (actual time=0.013..0.014 rows=1 loops=1)
   Output: id, data
   Filter: (((test.data ->> 'age'::text))::integer > 1)
   Buffers: shared hit=1
 Planning Time: 0.071 ms
 Execution Time: 0.028 ms
(6 rows)

函数索引

  • 代码
 -- 注意这里最外层有两个括号,否则会报错
create index idx_test_data_age on test using btree(((data->>'age')::int)); 
set enable_seqscan = 0;
select * from test where (data->>'age')::int>1;
explain (verbose, buffers) select * from test where (data->>'age')::int>1;

  • 演示
lxm=# create index idx_test_data_age on test using btree(((data->>'age')::int));
CREATE INDEX
lxm=# set enable_seqscan = 0;
SET
lxm=# select * from test where (data->>'age')::int>1;
 id |                                              data
----+-------------------------------------------------------------------------------------------------
  1 | {"age": 10,buffers) select * from test where (data->>'age')::int>1;
                                                           QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
 Index Scan using idx_test_data_age on public.test  (cost=0.12..8.14 rows=1 width=40) (actual time=0.017..0.019 rows=1 loops=1)
   Output: id, data
   Index Cond: (((test.data ->> 'age'::text))::integer > 1)
   Buffers: shared hit=2
 Planning Time: 0.145 ms
 Execution Time: 0.044 ms
(6 rows)

HASH 索引

哈希索引适用于等值查询

drop index if exists  idx_test_age;
-- 注意下面有三个括号,否则会报语法错误
create index idx_test_age on test using hash(((data->>'age')::int));
set enable_seqscan=0;
select * from test where (((data->>'age')::int))=10;
explain (verbose,buffers) select * from test where (((data->>'age')::int))=10;

  • 演示

    lxm=# drop index if exists  idx_test_age;
    DROP INDEX
    lxm=# create index idx_test_age on test using hash(((data->>'age')::int));
    explain (verbose,buffers) select * from test where (((data->>'age')::int))=10;
    
    CREATE INDEX
    lxm=# set enable_seqscan=0;
    SET
    lxm=# select * from test where (((data->>'age')::int))=10;
     id |                                              data                                               
    ----+-------------------------------------------------------------------------------------------------
      1 | {"age": 10,"name": "lxm","nick_name": ["xiaoming","phone_list": ["1111","2222"]}
    (1 row)
    
    lxm=# explain (verbose,buffers) select * from test where (((data->>'age')::int))=10;
                                                            QUERY PLAN                                                         
    ---------------------------------------------------------------------------------------------------------------------------
     Index Scan using idx_test_age on public.test  (cost=0.00..8.02 rows=1 width=40) (actual time=0.010..0.011 rows=1 loops=1)
       Output: id,data
       Index Cond: (((test.data ->> 'age'::text))::integer = 10)
       Buffers: shared hit=2
     Query Identifier: 1710720936157136870
     Planning Time: 0.047 ms
     Execution Time: 0.021 ms
    (7 rows)
    
    

参考:
https://scalegrid.io/blog/using-jsonb-in-postgresql-how-to-effectively-store-index-json-data-in-postgresql/
https://www.jianshu.com/p/96f78afb5a34

原文地址:https://blog.csdn.net/yueludanfeng/article/details/131477182

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读601次。Oracle的数据导入导出是一项基本的技能,但是对于懂数据库却不熟悉Oracle的同学可能会有一定的障碍。正好在最近的一个项目中碰到了这样一个任务,于是研究了一下Oracle的数据导入导出,在这里跟大家分享一下。......_oracle 迁移方法 对比
文章浏览阅读553次。开头还是介绍一下群,如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题,有需求都可以加群群内有各大数据库行业大咖,CTO,可以解决你的问题。加群请联系 liuaustin3 ,在新加的朋友会分到2群(共700多人左右 1 + 2)。最近我们在使用MYSQL 8 的情况下(8.025)在数据库运行中出现一个问题 参数prefer_order_i..._mysql prefer_ordering_index
文章浏览阅读3.5k次,点赞3次,收藏7次。折腾了两个小时多才成功连上,在这分享一下我的经验,也仅仅是经验分享,有不足的地方欢迎大家在评论区补充交流。_navicat连接opengauss
文章浏览阅读2.7k次。JSON 代表 JavaScript Object Notation。它是一种开放标准格式,将数据组织成中详述的键/值对和数组。_postgresql json
文章浏览阅读2.9k次,点赞2次,收藏6次。navicat 连接postgresql 注:navicat老版本可能报错。1.在springboot中引入我们需要的依赖以及相应版本。用代码生成器生成代码后,即可进行增删改查(略)安装好postgresql 略。更改配置信息(注释中有)_mybatisplus postgresql
文章浏览阅读1.4k次。postgre进阶sql,包含分组排序、JSON解析、修改、删除、更新、强制踢出数据库所有使用用户、连表更新与删除、获取今年第一天、获取近12个月的年月、锁表处理、系统表使用(查询所有表和字段及注释、查询表占用空间)、指定数据库查找模式search_path、postgre备份及还原_pgsql分组取每组第一条
文章浏览阅读3.3k次。上一篇我们学习了日志清理,日志清理虽然解决了日志膨胀的问题,但就无法再恢复检查点之前的一致性状态。因此,我们还需要日志归档,pg的日志归档原理和Oracle类似,不过归档命令需要自己配置。以下代码在postmaster.c除了开启归档外,还需要保证wal_level不能是MINIMAL状态(因为该状态下有些操作不会记录日志)。在db启动时,会同时检查archive_mode和wal_level。以下代码也在postmaster.c(PostmasterMain函数)。......_postgresql archive_mode
文章浏览阅读3k次。系统:ubuntu22.04.3目的:利用向日葵实现windows远程控制ubuntu。_csdn局域网桌面控制ubuntu
文章浏览阅读1.6k次。表分区是解决一些因单表过大引用的性能问题的方式,比如某张表过大就会造成查询变慢,可能分区是一种解决方案。一般建议当单表大小超过内存就可以考虑表分区了。1,继承式分区,分为触发器(trigger)和规则(rule)两种方式触发器的方式1)创建表CREATE TABLE "public"."track_info_trigger_partition" ( "id" serial, "object_type" int2 NOT NULL DEFAULT 0, "object_name..._pg数据表分区的实现
文章浏览阅读3.3k次。物联网平台开源的有几个,就我晓得的有、、thingskit、JetLink、DG-iot(还有其他开源的,欢迎在评论区留言哦!),然后重点分析了下ThingsBoard、ThingsPanel和JetLink,ThingsBoard和Jetlinks是工程师思维产品,可以更多的通过配置去实现开发的目的,ThingsPanel是业务人员思路产品,或者开发或者用,避免了复杂的配置带来的较高学习门槛。ThingsBoard和Jetlinks是Java技术体系的,ThingsPanel是PHP开发的。_jetlinks和thingsboard
文章浏览阅读3.8k次。PostgreSQL 数据类型转换_pgsql数字转字符串
文章浏览阅读7k次,点赞3次,收藏14次。在做数据统计页面时,总会遇到统计某段时间内,每天、每月、每年的数据视图(柱状图、折线图等)。这些统计数据一眼看过去也简单呀,不就是按照时间周期(天、月、年)对统计数据进行分个组就完了嘛?但是会有一个问题,简单的写个sql对周期分组,获取到的统计数据是缺失的,即没有数据的那天,整条记录也都没有了。如下图需求:以当前月份(2023年2月)为起点,往后倒推一年,查询之前一年里每个月的统计数据。可见图中的数据其实是缺少的,这条sql只查询到了有数据的月份(23年的1月、2月,22年的12月)_如何用一条sql查出按年按月按天的汇总
文章浏览阅读3.8k次,点赞66次,收藏51次。PostgreSQL全球开发小组与2022年10月13日,宣布发布PostgreSQL15,这是世界上最先进的开源数据库的最新版本_mysql8 postgresql15
文章浏览阅读1.3k次。上文介绍了磁盘管理器中VFD的实现原理,本篇将从上层角度讲解磁盘管理器的工作细节。_smgrrelationdata
文章浏览阅读1.1k次。PostgreSQL设置中文语言界面和局域网访问_postgressql汉化
文章浏览阅读4.2k次。PostgreSQL 修改数据存储路径_如何设置postgresql 数据目录
文章浏览阅读4.7k次。在项目中用到了多数据源,在连接postgres数据库时,项目启动报错,说数据库连接错误,说dual不存在,网上好多教程都是说数据库查询的时候的大小写问题,而这个仅仅是连接,咋鞥却处理方法是修改application-dev.yml中的配置文件.项目中的druid参数是这样的:确实在配置文件中有个查询语句。_relation "dual" does not exist
文章浏览阅读4.9k次。PostgreSQL是一款强大的关系型数据库,但在实际使用过程中,许多用户经常会遇到慢SQL的问题。这些问题不仅会降低数据库性能,还会直接影响业务流程和用户体验。因此,本文将会深入分析PostgreSQL慢SQL的原因和优化方案,帮助用户更好地利用这个优秀的数据库系统。无论你是初学者还是专业开发者,本文都将为你提供实用的技巧和方法,让你的PostgreSQL数据库始终保持高效快速。_postgresql数据库优化
文章浏览阅读1.6k次。Linux配置postgresql开机自启_linux 启动pgsql
文章浏览阅读2k次。本篇介绍如何在centos7系统搭建一个postgresql主备集群实现最近的HA(高可用)架构。后续更高级的HA模式都是基于这个最基本的主备搭建。_postgresql主备