本文将为您介绍Hologres中JSON和JSONB数据类型的语法和使用方法。

使用限制

Hologres支持JSON和JSONB两种JSON数据类型,在使用时需要注意的事项如下:
  • 仅Hologres V0.9及以上版本支持JSON类型,如果您的实例是V0.9以下版本,请您提交工单或加入在线支持钉钉群申请升级实例。
  • 仅Hologres V1.1及以上版本支持JSONB类型创建GIN索引,如果您的实例是V1.1以下版本,请您提交工单或加入在线支持钉钉群申请升级实例。
  • Hologres暂不支持的函数包括如下函数:json_each、jsonb_each、json_each_text、jsonb_each_text、json_extract_path、jsonb_extract_path、jsonb_to_record。
    如果您需要使用jsonb_extract_path和json_extract_path函数可以使用如下等价写法。
    • SELECT json_extract_path(
                              '{"key":{"key1":"key1","key2":"key2"}}'::json
                              , 'key'
                              , 'key1'
      );
      --json_extract_path函数的等价写法如下
      SELECT
          '{"key":{"key1":"key1","key2":"key2"}}'::json #> '{"key","key1"}';
    • SELECT jsonb_extract_path(
                  '{"key":{"key1":"key1","key2":"key2"}}'::jsonb
                  , 'key'
                  , 'key1'
      );
      -- jsonb_extract_path函数的等价写法如下
      SELECT
          '{"key":{"key1":"key1","key2":"key2"}}'::jsonb #> '{"key","key1"}';                                    
  • 仅Hologres V1.3及以上版本支持对JSONB类型进行列存优化,若您的实例是V1.3以下版本,请您提交工单或加入在线支持钉钉群申请升级实例。
  • JSONB的列存优化仅能用于列存表,行存表暂不支持,且至少1000条数据才会触发列存优化。

JSON和JSONB类型概述

JSON和JSONB区别如下。
  • JSON储存的是文本格式的数据,JSONB储存的是Binary格式的数据。
  • JSON插入速度快,查询速度慢,原因是处理函数必须在每次执行时重新解析该数据。JSONB插入速度慢,而查询速度快,原因是JSONB数据被存储在一种分解好的二进制格式中,因为需要做附加的转换,它在输入时要稍慢一些。但是JSONB在查询数据时快很多,因为不需要重新解析。
  • JSON储存的数据是对数据的完整拷贝,会保留源数据的空格、重复键和顺序等,如果一个值中的JSON对象包含同一个键超过一次,所有的键、值对都会被保留。而JSONB在解析时会删除掉不必要的空格、重复键和数据的顺序等,如果在输入中指定了重复的键,只有最后一个值会被保留。
说明 键值对的键必须使用双引号。

JSON和JSONB的操作符

JSON数据类型用来存储JSON数据,这种数据也可以被存储为TEXT类型。JSON数据类型的优势在于能强制要求每个被存储的值符合JSON规则,JSON支持的操作符使得其操作更为方便。
操作符 右操作数类型 描述 操作示例 执行结果
-> int 获得JSON数组元素(索引从0开始,负整数从末尾开始计)。 select '[{"a":"foo"},{"b":"bar"},{"c":"baz"}]'::json->2 {"c":"baz"}
-> text 通过键获得JSON对象域。 select '{"a": {"b":"foo"}}'::json->'a' {"b":"foo"}
->> int 以TEXT形式获得JSON数组元素。 select '[1,2,3]'::json->>2 3
->> text 以TEXT形式获得JSON对象域。 select '{"a":1,"b":2}'::json->>'b' 2
#> text[] 获取在指定路径的JSON对象。 select '{"a": {"b":{"c": "foo"}}}'::json#>'{a,b}' {"c":"foo"}
#>> text[] 以TEXT形式获取在指定路径的JSON对象。 select '{"a":[1,2,3],"b":[4,5,6]}'::json#>>'{a,2}' 3

额外的JSONB操作符

如下表格中为JSONB数据类型支持的操作符。
操作符 右操作数类型 描述 操作示例 执行结果
@> jsonb 左侧的JSON值是否在顶层包含右侧的JSON路径或值。 select '{"a":1, "b":2}'::jsonb @> '{"b":2}'::jsonb true
<@ jsonb 左侧的JSON路径或值项是否被包含在右侧的JSON 值的顶层。 select '{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb true
? text 键或元素字符串是否存在于JSON值的顶层。 select '{"a":1, "b":2}'::jsonb ? 'b' true
?| text[] 数组字符串中的任何一个是否做为顶层键存在。 select '{"a":1, "b":2, "c":3}'::jsonb ?| array['b', 'c'] true
?& text[] 是否所有数组字符串都作为顶层键存在。 select '["a", "b"]'::jsonb ?& array['a', 'b'] true
|| jsonb 将两个jsonb值串接成一个新的jsonb值。 select '["a", "b"]'::jsonb || '["c", "d"]'::jsonb ["a", "b", "c", "d"]
- text 从左操作数删除键/值对或者string元素。键/值对基于它们的键值来匹配。 select '{"a": "b"}'::jsonb - 'a' {}
- text[] 从左操作数中删除多个键/值对或者string元素。键/值对基于它们的键值来匹配。 select '{"a": "b", "c": "d"}'::jsonb - '{a,c}'::text[] {}
- integer 删除具有指定索引(负值表示倒数)的数组元素。如果顶层容器不是数组则抛出一个错误。 select '["a", "b"]'::jsonb - 1 ["a"]
#- text[] 删除具有指定路径的域或者元素(对于JSON数组,负值表示倒数)。 select '["a", {"b":1}]'::jsonb #- '{1,b}' ["a", {}]

JSON创建函数

如下为可以用于创建JSON值的函数描述及操作示例。
函数 描述 操作示例 执行结果
to_json(anyelement) 此函数可以将该值返回为JSON。数组和组合会被(递归)转换成数组和对象,对于不是数组和组合的值,如果有从该类型到JSON的造型,造型函数将被用来执行该转换,否则将产生一个标量值。对于任何不是数字、布尔、空值的标量类型,将使用文本表达,使其是一个有效的JSON值。 select to_json('Fred said "Hi."'::text) "Fred said \"Hi.\""
to_jsonb(anyelement)
array_to_json(anyarray [, pretty_bool]) 此函数可以将数组作为一个JSON数组返回。一个PostgreSQL多维数组会成为一个数组的JSON数组。如果pretty_bool为真,将在第1维度的元素之间增加换行。 select array_to_json('{{1,5},{99,100}}'::int[]) [[1,5],[99,100]]
json_build_array(VARIADIC "any") 此函数可以从一个可变参数列表构造一个可能包含异质类型的JSON数组。 select json_build_array(1,2,'3',4,5) [1, 2, "3", 4, 5]
jsonb_build_array(VARIADIC "any")
json_build_object(VARIADIC "any") 此函数可以从一个可变参数列表构造一个JSON对象。通过转换,该参数列表由交替出现的键和值构成。 select json_build_object('foo',1,'bar',2) {"foo": 1, "bar": 2}
jsonb_build_object(VARIADIC "any")
json_object(text[]) 此函数可以从一个文本数组构造一个JSON对象。该数组必须可以是具有偶数个成员的一维数组(成员被当做交替出现的键/值对),或者是一个二维数组(每一个内部数组刚好有2个元素,可以被看做是键/值对)。 select json_object('{a, 1, b, "def", c, 3.5}'); {"a": "1", "b": "def", "c": "3.5"}
jsonb_object(text[]) select jsonb_object('{a, 1, b, "def", c, 3.5}'); {"a": "1", "b": "def", "c": "3.5"}
json_object(keys text[], values text[]) json_object的这种形式从两个独立的数组得到键/值对。在其他方面和一个参数的形式相同。 select json_object('{a, b}', '{1,2}') {"a": "1", "b": "2"}
jsonb_object(keys text[], values text[])

JSON处理函数

如下为可以用于处理JSON值的函数描述及操作示例。
函数 返回值 描述 操作示例 执行结果
json_array_length(json) int 返回最外层JSON数组中的元素数量。 select json_array_length('[1,2,3,{"f1":1,"f2":[5,6]},4]') 5
jsonb_array_length(jsonb)
json_object_keys(json) setof text 返回最外层JSON对象中的键集合。 select json_object_keys('{"f1":"abc","f2":{"f3":"a", "f4":"b"}}')
json_object_keys
------------------
 f1
 f2
jsonb_object_keys(jsonb)
json_populate_record(base anyelement, from_json json) anyelement 扩展from_json中的对象成一个行,它的列匹配由base定义的记录类型。
begin;
create table 
myrowtype( a text, b text, c text);commit;
select * from json_populate_record(null::myrowtype, '{"a": 1, "b": ["2", "a b"], "c": {"d": 4, "e": "a b c"}}')
 a |   b       |      c
---+-----------+-------------
 1 | {2,"a b"} | {"d": 4, "e": "a b c"}
jsonb_populate_record(base anyelement, from_json jsonb)
json_populate_recordset(base anyelement, from_json json) setof anyelement 扩展from_json中最外的对象数组为一个集合,该集合的列匹配由base定义的记录类型。
begin;
create table 
myrowtype(a text,b text);
commit;
select * from json_populate_recordset(null::myrowtype, '[{"a":1,"b":2},{"a":3,"b":4}]')
 a | b
---+---
 1 | 2
 3 | 4
jsonb_populate_recordset(base anyelement, from_json jsonb)
json_array_elements(json) setof json 把一个JSON数组扩展成一个JSON值的集合。 select * from json_array_elements('[1,true, [2,false]]')
   value
-----------
 1
 true
 [2,false]
jsonb_array_elements(jsonb) setof jsonb
json_array_elements_text(json) setof text 把一个JSON数组扩展成一个text值集合。 select * from json_array_elements_text('["foo", "bar"]')
 value
-----------
 foo
 bar
jsonb_array_elements_text(jsonb)
json_typeof(json) text 把最外层的JSON值的类型作为一个文本字符串返回。可能的类型是: object、array、string、number、 boolean以及null。 select json_typeof('-123.4') number
jsonb_typeof(jsonb)
json_strip_nulls(from_json json) json 返回from_json,其中所有具有空值的对象域都被省略。其他空值不动。 select json_strip_nulls('[{"f1":1,"f2":null},2,null,3]') [{"f1":1},2,null,3]
jsonb_strip_nulls(from_json jsonb) jsonb
jsonb_set(target jsonb, path text[], new_value jsonb[,create_missing boolean]) jsonb 返回target,其中由path指定的节用new_value替换,如果path指定的项不存在并且create_missing为真(默认为 true)则加上new_value。正如面向路径的操作符一样,出现在path中的负整数表示从JSON数组的末尾开始数。 select jsonb_set('[{"f1":1,"f2":null},2,null,3]', '{0,f1}','[2,3,4]', false); [{"f1":[2,3,4],"f2":null},2,null,3]
select jsonb_set('[{"f1":1,"f2":null},2]', '{0,f3}','[2,3,4]') [{"f1": 1, "f2": null, "f3": [2, 3, 4]}, 2]
jsonb_insert(target jsonb, path text[], new_value jsonb, [insert_after boolean]) jsonb 返回被插入了new_value的target。如果path指定的target节在一个JSONB数组中,new_value将被插入到目标之前(insert_after为false,默认情况)或者之后(insert_after为真)。如果path指定的target节在一个JSONB对象内,则只有当target不存在时才插入new_value。对于面向路径的操作符来说,出现在path中的负整数表示从JSON数组的末尾开始计数。 select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"') {"a": [0, "new_value", 1, 2]}
select jsonb_insert('{"a": [0,1,2]}', '{a, 1}', '"new_value"', true) {"a": [0, 1, "new_value", 2]}
jsonb_pretty(from_json jsonb) text 把from_json返回成一段缩进后的JSON文本。 select jsonb_pretty('[{"f1":1,"f2":null},2,null,3]')
[
    {
        "f1": 1,
        "f2": null
    },
    2,
    null,
    3
]
jsonb_agg jsonb 将值(包括空值)聚合为JSON数组。
DROP TABLE IF EXISTS t;

CREATE TABLE t (
    k int PRIMARY KEY,
    class int NOT NULL,
    v text NOT NULL
);

INSERT INTO t (k, class, v)
SELECT
    (1 + s.v),
    CASE (s.v) < 3
    WHEN TRUE THEN
        1
    ELSE
        2
    END,
    chr(97 + s.v)
FROM
    generate_series(0, 5) AS s (v);

SELECT
    class,
    jsonb_agg(v ORDER BY v DESC) FILTER (WHERE v <> 'b') AS "jsonb_agg",
    jsonb_object_agg(v, k ORDER BY v DESC) FILTER (WHERE v <> 'e') AS "jsonb_object_agg(v, k)"
FROM
    t
GROUP BY
    class;
 class |    jsonb_agg    |  jsonb_object_agg(v, k)
-------+-----------------+--------------------------
     1 | ["c", "a"]      | {"a": 1, "b": 2, "c": 3}
     2 | ["f", "e", "d"] | {"d": 4, "f": 6}
jsonb_object_agg jsonb 将Key/Value对聚合为JSON对象,值可以为空,但名称不能为空。
DROP TABLE IF EXISTS t;

CREATE TABLE t (
    k int PRIMARY KEY,
    class int NOT NULL,
    v text NOT NULL
);

INSERT INTO t (k, class, v)
SELECT
    (1 + s.v),
    CASE (s.v) < 3
    WHEN TRUE THEN
        1
    ELSE
        2
    END,
    chr(97 + s.v)
FROM
    generate_series(0, 5) AS s (v);

SELECT
    class,
    jsonb_agg(v ORDER BY v DESC) FILTER (WHERE v <> 'b') AS "jsonb_agg",
    jsonb_object_agg(v, k ORDER BY v DESC) FILTER (WHERE v <> 'e') AS "jsonb_object_agg(v, k)"
FROM
    t
GROUP BY
    class;
 class |    jsonb_agg    |  jsonb_object_agg(v, k)
-------+-----------------+--------------------------
     1 | ["c", "a"]      | {"a": 1, "b": 2, "c": 3}
     2 | ["f", "e", "d"] | {"d": 4, "f": 6}
is_valid_json BOOLEAN IS_VALID_JSON函数用于验证JSON字符串,如果字符串格式是正确的JSON字符串,则该函数返回布尔值true(t);如果字符串格式不正确,函数将返回false (f)。
说明
  • 仅Hologres V1.3.12及以上版本支持此函数。
  • ARRAY类型也算是一种JSON类型,也可以被写入JSON/JSONB类型的列。
DROP TABLE IF EXISTS test_json;

CREATE TABLE test_json (
    id int,
    json_strings text
);

INSERT INTO test_json
    VALUES (1, '{"a":2}'), (2, '{"a":{"b":{"c":1}}}'), (3, '{"a": [1,2,"b"]}');

INSERT INTO test_json
    VALUES (4, '{{}}'), (5, '{1:"a"}'), (6, '[1,2,3]');

SELECT
    id,
    json_strings,
    is_valid_json (json_strings)
FROM
    test_json
ORDER BY
    id;
id | json_strings        | is_valid_json
---+---------------------+--------------
 0 | {"a":2}             | true
 2 | {"a":{"b":{"c":1}}} | true
 4 | {"a": [1,2,"b"]}    | true
 6 | {{}}                | false
 8 | {1:"a"}             | false
10 | [1,2,3]             | true

更多关于JSON类型的用法,请参见JSON函数和操作符

JSONB索引

从Hologres V1.1版本开始,支持JSONB索引,如下为您介绍在JSONB类型字段上建GIN索引的语法解释和使用示例。

  • 语法解释
    JSONB类型支持GIN,BTree索引。一般情况下,会在JSONB类型字段上建GIN索引,建GIN索引存在三种方式,语法分别如下。
    • 使用默认的jsonb_ops操作符创建索引
      CREATE INDEX idx_name ON table_name USING gin (idx_col);
    • 使用jsonb_path_ops操作符创建索引
      CREATE INDEX idx_name ON table_name USING gin (idx_col jsonb_path_ops);
    • 使用jsonb_holo_path_ops操作符创建索引
      CREATE INDEX idx_name ON table_name USING gin (idx_col jsonb_holo_path_ops);
    三种方式的区别为:在jsonb_ops的GIN索引中,JSONB数据中的每个key和value都是作为一个单独的索引项的;而jsonb_path_ops则只为每个value创建一个索引项;jsonb_holo_path_ops为Hologres全新的操作符号,可以省去检索数据后recheck的动作。
  • 使用示例
    • 原生PostgreSQL操作符
      • 创建jsonb_ops操作符号索引
        1. 创建表的SQL语句如下。
          DROP TABLE IF EXISTS json_table;
          CREATE TABLE IF NOT EXISTS json_table
          (
              id INT
              ,j jsonb
          );
        2. 创建jsonb_ops操作符号索引的SQL语句如下。
          CREATE INDEX index_json on json_table USING GIN(j);
        3. 在表中插入数据的SQL语句如下。
          INSERT INTO json_table VALUES
          (1, '{"key1": 1, "key2": [1, 2], "key3": {"a": "b"}}') ,
          (1, '{"key1": 1}'),
          (2, '{"key2": [1, 2], "key3": {"a": "b"}}') ;
        4. 筛选包含数据的SQL语句如下。
          SELECT  *
          FROM    json_table
          WHERE   j ? 'key1'
          ;
        5. 执行结果如下。
           id |                        j                        
          ----+-------------------------------------------------
            1 | {"key1": 1, "key2": [1, 2], "key3": {"a": "b"}}
            1 | "key1"
          (2 rows)
        6. 使用explain命令查看执行计划如下。
                                                  QUERY PLAN
          -------------------------------------------------------------------------------------------
           Gather  (cost=0.00..12.36 rows=1 width=66)
             ->  Exchange (Gather Exchange)  (cost=0.00..12.36 rows=1 width=66)
                   ->  Decode  (cost=0.00..12.36 rows=1 width=66)
                         ->  Bitmap Heap Scan on json_table  (cost=0.00..12.26 rows=1 width=66)
                               Recheck Cond: (j ? 'key1'::text)
                               ->  Bitmap Index Scan on index_json  (cost=0.00..0.00 rows=0 width=0)
                                     Index Cond: (j ? 'key1'::text)
           Optimizer: HQO version 1.1.0
          (8 rows)
          执行计划中出现了Index Scan步骤,表明查询过程使用了索引。
      • 创建jsonb_path_ops操作符号索引
        1. 创建表的SQL语句如下。
          DROP TABLE IF EXISTS json_table;
          CREATE TABLE IF NOT EXISTS json_table
          (
              id INT
              ,j jsonb
          );
        2. 创建jsonb_path_ops操作符号索引的SQL语句如下。
          CREATE INDEX index_json on json_table USING GIN(j jsonb_path_ops);
        3. 插入数据的SQL语句如下。
          INSERT INTO json_table (
              SELECT 
                  i, 
                  ('{
                      "key1": "'||i||'"
                      ,"key2": "'||i%100||'"
                      ,"key3": "'||i%1000 ||'"
                      ,"key4": "'||i%10000||'"
                      ,"key5": "'||i%100000||'"
                  }')::jsonb 
              FROM generate_series(1, 1000000) i
          ) ;
        4. 筛选包含'{"key1": "10"}'数据的SQL语句如下。
          SELECT  *
          FROM    json_table
          WHERE   j @> '{"key1": "10"}'::JSONB 
          ;
        5. 执行结果如下。
           id |                                   j                                    
          ----+------------------------------------------------------------------------
           10 | {"key1": "10", "key2": "10", "key3": "10", "key4": "10", "key5": "10"}
          (1 row)
        6. 使用explain命令查看执行计划如下。
                                                  QUERY PLAN
          -------------------------------------------------------------------------------------------
           Gather  (cost=0.00..0.26 rows=1000 width=12)
             ->  Exchange (Gather Exchange)  (cost=0.00..0.23 rows=1000 width=12)
                   ->  Decode  (cost=0.00..0.23 rows=1000 width=12)
                         ->  Bitmap Heap Scan on json_table  (cost=0.00..0.13 rows=1000 width=12)
                               Recheck Cond: (j @> '{"key1": "10"}'::jsonb)
                               ->  Bitmap Index Scan on index_json  (cost=0.00..0.00 rows=0 width=0)
                                     Index Cond: (j @> '{"key1": "10"}'::jsonb)
           Optimizer: HQO version 1.1.0
          (8 rows)
          执行计划中出现了Index Scan步骤,表明查询过程使用了索引。
    • Hologres操作符

      由于原生PostgreSQL的JSONB的GIN索引是非精确的索引,所以检索数据后需要进行recheck动作。最终导致创建索引后性能不一定提升。针对上述情况,Hologres实现了一种新的ops_class,可以省去recheck的动作,且若不指定索引操作符,系统会默认使用该操作符,具体使用方式如下。

      其中jsonb_holo_ops对应jsonb_ops,支持?, ?|, ?&, @>的过滤操作。其中jsonb_holo_path_ops对应jsonb_path_ops,仅支持@>的过滤操作。

      • 创建jsonb_holo_ops操作符号索引
        1. 创建表的SQL语句如下。
          DROP TABLE IF EXISTS json_table;
          CREATE TABLE IF NOT EXISTS json_table
          (
              id INT
              ,j jsonb
          );
        2. 创建jsonb_holo_ops操作符号索引的SQL语句如下。
          -- 创建索引,使用jsonb_holo_ops操作符
          CREATE INDEX index_json on json_table USING GIN(j jsonb_holo_ops);
        3. 插入数据的SQL语句如下。
          INSERT INTO json_table VALUES
          (1, '{"key1": 1, "key2": [1, 2], "key3": {"a": "b"}}') ,
          (1, '{"key1": 1}'),
          (2, '{"key2": [1, 2], "key3": {"a": "b"}}') ;
        4. 筛选包含数据的SQL语句如下。
          SELECT  *
          FROM    json_table
          WHERE   j ? 'key1'
          ;
        5. 执行结果如下。
           id |                        j
          ----+-------------------------------------------------
            1 | {"key1": 1, "key2": [1, 2], "key3": {"a": "b"}}
            1 | {"key1": 1}
          (2 rows) 
      • 创建jsonb_holo_path_ops操作符号索引
        1. 创建表的SQL语句如下。
          DROP TABLE IF EXISTS json_table;
          CREATE TABLE IF NOT EXISTS json_table
          (
              id INT
              ,j jsonb
          );
        2. 创建jsonb_holo_path_ops操作符号索引的SQL语句如下。
          CREATE INDEX index_json on json_table USING GIN(j jsonb_holo_path_ops);
        3. 插入数据的SQL语句如下。
          INSERT INTO json_table (
              SELECT 
                  i, 
                  ('{
                      "key1": "'||i||'"
                      ,"key2": "'||i%100||'"
                      ,"key3": "'||i%1000 ||'"
                      ,"key4": "'||i%10000||'"
                      ,"key5": "'||i%100000||'"
                  }')::jsonb 
              FROM generate_series(1, 1000000) i
          ) ;
        4. 筛选包含'{"key1": "10"}'数据的SQL语句如下。
          SELECT  *
          FROM    json_table
          WHERE   j @> '{"key1": "10"}'::JSONB 
          ;
        5. 执行结果如下。
           id |                                   j                                    
          ----+------------------------------------------------------------------------
           10 | {"key1": "10", "key2": "10", "key3": "10", "key4": "10", "key5": "10"}
          (1 row)
        6. 使用explain命令查看执行计划如下。
          QUERY PLAN
          Gather  (cost=0.00..39038928.99 rows=400000 width=88)
            ->  Exchange (Gather Exchange)  (cost=0.00..39038843.49 rows=400000 width=88)
                  ->  Decode  (cost=0.00..39038843.37 rows=400000 width=88)
                        ->  Bitmap Heap Scan on json_table  (cost=0.00..39038840.00 rows=400000 width=88)
          "                    Recheck Cond: (j @> '{"key1": "10"}'::jsonb)"
                              ->  Bitmap Index Scan on index_json  (cost=0.00..0.00 rows=0 width=0)
          "                          Index Cond: (j @> '{"key1": "10"}'::jsonb)"
          Optimizer: HQO version 0.10.0
          执行计划中出现了Index Scan步骤,表明查询过程使用了索引。

JSONB列式存储优化

从Hologres V1.3版本开始,支持对于JSONB类型开启列存优化,能够减少JSONB的存储并加速查询。

如下图示例,开启列式存储后底层会自动将Schemaless的Jsonb列,转换为强Schema的列式存储,查询时就可以直接命中查询的列,提升查询性能。列式存储优化
具体使用方法如下。
  • 语法解释。
    • 对某张表的某个列打开JSONB列存优化。
      -- 打开xx表的xx列的JSONB列式存储优化
      ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_columnar_type = ON);
      table_name为表名称;column_name为列名称。
      重要 打开JSONB列存优化后,系统在Compaction时将历史数据都转为列存,待Compaction完毕即完成历史数据的列存化。同时新写入的数据会按照列存存储。您可以使用vacuum <table_name>;命令强制触发Compaction操作,由于Compaction会消耗系统资源,建议在业务低峰期操作。待vacuum命令执行完毕,Compaction操作就执行完毕。
    • 关闭JSONB列存优化。
      -- 关闭xx表的xx列的JSONB列式存储优化
      ALTER TABLE <table_name> ALTER COLUMN <column_name> SET (enable_columnar_type = OFF);
      table_name为表名称;column_name为列名称。
      重要 关闭JSONB列存优化后,系统在Compaction时将历史数据都转为标准的JSONB存储方式,待Compaction完毕即完成历史数据的转换。同时新写入的数据会按照标准的JSONB存储方式存储。您可以使用vacuum <table_name>;命令强制触发Compaction操作,由于Compaction会消耗系统资源,建议在业务低峰期操作。待vacuum命令执行完毕,Compaction操作就执行完毕。
    • 查看某张表的配置情况
      SELECT DISTINCT
          a.attnum as num,
          a.attname as name,
          format_type(a.atttypid, a.atttypmod) as type,
          a.attnotnull as notnull,
          com.description as comment,
          coalesce(i.indisprimary,false) as primary_key,
          def.adsrc as default,
          a.attoptions
      FROM pg_attribute a
      JOIN pg_class pgc ON pgc.oid = a.attrelid
      LEFT JOIN pg_index i ON
          (pgc.oid = i.indrelid AND i.indkey[0] = a.attnum)
      LEFT JOIN pg_description com on
          (pgc.oid = com.objoid AND a.attnum = com.objsubid)
      LEFT JOIN pg_attrdef def ON
          (a.attrelid = def.adrelid AND a.attnum = def.adnum)
      WHERE a.attnum > 0 AND pgc.oid = a.attrelid
      AND pg_table_is_visible(pgc.oid)
      AND NOT a.attisdropped
      AND pgc.relname = '<table_name>'  -- 表名称
      ORDER BY a.attnum;
      table_name为表名称。

      返回结果可以看到某个列的attoptions属性为enable_columnar_type = ON,表示已经配置成功。

  • 使用示例
    1. 创建表SQL命令如下。
      DROP TABLE IF EXISTS user_tags;
      
      -- 创建数据表
      BEGIN;
      CREATE TABLE IF NOT EXISTS user_tags (
          ds timestamptz,
          tags jsonb
      );
      COMMIT;
    2. 打开JSONB列存优化SQL命令如下。
      -- 打开tags列的JSONB列存优化
      ALTER TABLE user_tags ALTER COLUMN tags SET (enable_columnar_type = ON);
    3. 查看是否设置成功
      • 查看配置命令如下。
        SELECT DISTINCT
            a.attnum as num,
            a.attname as name,
            format_type(a.atttypid, a.atttypmod) as type,
            a.attnotnull as notnull,
            com.description as comment,
            coalesce(i.indisprimary,false) as primary_key,
            def.adsrc as default,
            a.attoptions
        FROM pg_attribute a
        JOIN pg_class pgc ON pgc.oid = a.attrelid
        LEFT JOIN pg_index i ON
            (pgc.oid = i.indrelid AND i.indkey[0] = a.attnum)
        LEFT JOIN pg_description com on
            (pgc.oid = com.objoid AND a.attnum = com.objsubid)
        LEFT JOIN pg_attrdef def ON
            (a.attrelid = def.adrelid AND a.attnum = def.adnum)
        WHERE a.attnum > 0 AND pgc.oid = a.attrelid
        AND pg_table_is_visible(pgc.oid)
        AND NOT a.attisdropped
        AND pgc.relname = 'user_tags'  -- 表名
        ORDER BY a.attnum;
      • 结果
        可以看到tags行的attoptions属性是enable_columnar_type = on,表示已经配置成功。列优化配置成功
    4. 导入数据SQL命令如下。
      INSERT INTO user_tags (ds, tags)
      SELECT
          '2022-01-01 00:00:00+08'
          , ('{"id":' || i || ',"first_name" :"Sig",  "gender" :"Male"}')::jsonb
      FROM
          generate_series(1, 1001) i;
    5. (可选)强制触发数据落盘。
      写入数据后,系统会在数据落盘时进行JSONB的列存优化,为了尽快看到效果,此处使用如下后台命令,强制触发数据落盘。
      SELECT hg_admin_command ('flush', 'table_name=user_tags');
      
      VACUUM user_tags;
    6. 样例查询
      使用如下SQL查询id10first_name
      SELECT
          (tags -> 'first_name')::text AS first_name
      FROM
          user_tags
      WHERE (tags -> 'id')::int = 10;
    7. 查看列存优化是否生效
      • 通过如下命令查看执行计划。
        -- 显示详细的统计信息
        SET hg_experimental_show_execution_statistics_in_explain = ON;
        -- 查看执行计划
        EXPLAIN ANALYZE
        SELECT
            (tags -> 'first_name')::text AS first_name
        FROM
            user_tags
        WHERE (tags -> 'id')::int = 10;
      • 结果
        执行计划如下所示,其中结果中有columnar_access_used,表示JSONB列存优化生效。执行计划