注册
DM8 原生 JSON 类型
专栏/技术分享/ 文章详情 /

DM8 原生 JSON 类型

巨浪 2026/09/23 333 0 0
摘要

一、环境与验证范围

业务里半结构化的数据越来越多,配置项、标签、接口报文都不适合为每个字段建表。DM8 提供原生 JSON 类型和一组 JSON 函数,本文在本机独立实例 ADVLAB 上逐项验证。实例版本为 DM Database Server 64 V8,DB Version 0x7000d,构建号 20260707。下面每条语句都在该实例实际执行,结果由 DIsql 与 JDBC 采集。

验证范围包括 JSON 列、JSON_VALUE、JSON_QUERY、JSON_EXISTS、JSON_TABLE、写入合法性校验、JSON_SET 等修改函数,以及针对 JSON 字段的函数索引。

二、建表与标量提取

建表时可以直接把列声明为 JSON,写入的字符串会被当作 JSON 文档保存。

代码 1 JSON 列建表与写入

CREATE TABLE T\_JSON(ID INT PRIMARY KEY, DOC JSON);

INSERT INTO T\_JSON VALUES(1,'{"name":"zhang","age":28,"dept":"RD","tags":\["a","b"]}');

INSERT INTO T\_JSON VALUES(2,'{"name":"li","age":34,"dept":"SALES","tags":\["c"]}');

COMMIT;

JSON_VALUE 按路径取标量,路径以 $ 表示文档根,$.tags[0] 表示 tags 数组的第一个元素。JSON_QUERY 取对象或数组,返回的仍是 JSON 文本。

代码 2 JSON_VALUE 与 JSON_QUERY 的实测结果

SELECT ID,

       JSON\_VALUE(DOC,'\$.name')    NAME,

       JSON\_VALUE(DOC,'\$.age')     AGE,

       JSON\_VALUE(DOC,'\$.tags\[0]') TAG0

FROM T\_JSON;
 ID  NAME    AGE  TAG0

  1  zhang    28  a

  2  li       34  c
SELECT ID, JSON\_QUERY(DOC,'\$.tags') TAGS FROM T\_JSON;
 ID  TAGS

  1  \["a","b"]

  2  \["c"]

两者的分工是:要拿一个可直接参与运算、比较的单值,用 JSON_VALUE;要取出整段对象或数组,用 JSON_QUERY。JSON_EXISTS 只判断路径是否存在,适合做过滤条件。

三、用 JSON_TABLE 把文档展开成关系行

半结构化数据要做报表或与关系表关联时,需要把数组展开成多行,JSON_TABLE 就是做这件事的。

代码 3 展开 tags 数组,一条文档对应多行

SELECT ID, jt.tag

FROM T\_JSON,

     JSON\_TABLE(T\_JSON.DOC,'\$.tags\[\*]'

       COLUMNS(tag VARCHAR(10) PATH '\$')) jt;
 ID  TAG

  1  a

  1  b

  2  c

订单这类带明细数组的文档,可以在 COLUMNS 里声明多列,展开后还能直接做列运算。

代码 4 嵌套明细展开并计算金额

SELECT d.ID, JSON\_VALUE(d.DOC,'\$.cust') CUST,

       jt.p, jt.qty, jt.price, jt.qty\*jt.price AMT

FROM T\_ORDER\_DOC d,

     JSON\_TABLE(d.DOC,'\$.items\[\*]' COLUMNS(

       p     VARCHAR(20)    PATH '\$.p',

       qty   INT            PATH '\$.qty',

       price DECIMAL(10,2)  PATH '\$.price')) jt;
 ID  CUST    P          QTY  PRICE    AMT

  1  zhang   mouse        2   80.00   160

  1  zhang   keyboard     1  200.00   200

  2  li      mouse        1   80.00    80

四、合法性校验与文档更新

JSON 列在写入时做语法校验,非法文档进不来,不需要在应用层各自实现检查。实测插入一段格式错误的文本,语句被拒绝。

代码 5 非法 JSON 被拒绝,错误码 -3105

INSERT INTO T\_JSON VALUES(3,'{not valid json');

\-- \[-3105]: JSON 值语法错误。

修改文档有四个函数,区别在遇到字段已存在或不存在时的处理。

函数 字段已存在 字段不存在 典型用途
JSON_SET 替换 新增 不确定字段是否存在时统一设置
JSON_REPLACE 替换 忽略 只改已有字段
JSON_INSERT 忽略 新增 只补缺失字段
JSON_REMOVE 删除 无操作 去掉指定路径

代码 6 四个修改函数的字面量实测

SELECT JSON\_SET('{"a":1,"b":2}','\$.a',9)   J\_SET,

       JSON\_REPLACE('{"a":1}','\$.a',9)     J\_REP,

       JSON\_INSERT('{"a":1}','\$.c',3)      J\_INS,

       JSON\_REMOVE('{"a":1,"b":2}','\$.b')  J\_RM;
 J\_SET          J\_REP   J\_INS          J\_RM

 {"a":9,"b":2}  {"a":9} {"a":1,"c":3}  {"a":1}

这些函数返回修改后的文档,配合 UPDATE 落回表中。

代码 7 给文档新增 status 字段

UPDATE T\_ORDER\_DOC

   SET DOC=JSON\_INSERT(DOC,'\$.status','paid')

 WHERE ID=1;

SELECT ID, JSON\_VALUE(DOC,'\$.status') ST FROM T\_ORDER\_DOC;
 ID  ST

  1  paid

  2  NULL

五、JSON 查询的函数索引

JSON 字段的过滤条件本质是函数表达式,默认不会走普通索引。构造一张 50 万行的表,每行一条含 name、age、dept、tags 的文档。

代码 8 构造 50 万行 JSON 数据,实测加载约 2.6 秒

CREATE TABLE T\_ORDER\_JSON(ID INT PRIMARY KEY, DOC JSON);

INSERT INTO T\_ORDER\_JSON

SELECT ROWNUM,

       '{"name":"u'||MOD(ROWNUM,500)||'","age":'||(20+MOD(ROWNUM,40))

       ||',"dept":"'||CASE WHEN MOD(ROWNUM,2)=0 THEN 'RD' ELSE 'SALES' END

       ||'","tags":\["t'||MOD(ROWNUM,10)||'"]}'

&#x20; FROM SYSDUAL CONNECT BY ROWNUM<=500000;

COMMIT;

没有索引时,按 name 过滤是全表扫描,CSCN2 要扫完 50 万行。

代码 9 无索引时的执行计划(末位为扫描行数,代价 63)

EXPLAIN SELECT ID FROM T\_ORDER\_JSON

&#x20; WHERE JSON\_VALUE(DOC,'\$.name')='u1';
1   #NSET2: \[63, 12500, 64]

2     #PRJT2: \[63, 12500, 64]

3       #SLCT2: \[63, 12500, 64]; exp11 = 'u1'

4         #CSCN2: \[63, 500000, 64]; INDEX33555501(T\_ORDER\_JSON)

对 JSON_VALUE 表达式建函数索引,再收集统计信息,让优化器知道每个 name 的数据量。

代码 10 建函数索引并收集统计

CREATE INDEX IDX\_JSON\_NAME

&#x20; ON T\_ORDER\_JSON(JSON\_VALUE(DOC,'\$.name'));

CALL DBMS\_STATS.GATHER\_TABLE\_STATS('SYSDBA','T\_ORDER\_JSON');

代码 11 建索引后的执行计划(范围扫描约 995 行,代价 1)

EXPLAIN SELECT ID FROM T\_ORDER\_JSON

&#x20; WHERE JSON\_VALUE(DOC,'\$.name')='u1';
1   #NSET2: \[1, 995, 64]

2     #PRJT2: \[1, 995, 64]

3       #BLKUP2: \[1, 995, 64]; IDX\_JSON\_NAME(T\_ORDER\_JSON)

4         #SSEK2: \[1, 995, 64]; scan\_range\['u1','u1']
对比项 无索引 函数索引
访问路径 CSCN2 全表扫描 SSEK2 范围扫描,BLKUP2 回表
扫描行数 500000 约 995
计划代价 63 1

image.png

说明:

50 万行短文档在本实例连续执行后会驻留缓冲池,毫秒计时差异很小甚至接近 0,不能作为性能依据。稳定的判据是执行计划的访问路径、扫描行数和逻辑读。计划中的 BLKUP2 是回表取 ID,若查询只返回索引键还能进一步减少回表。

六、使用建议

结构固定、频繁参与连接和约束的数据,仍按关系表建模;JSON 列适合结构多变、以整文档读取为主的场景。

需要按 JSON 内字段高频过滤,就对该 JSON_VALUE 建函数索引,并在批量数据变化后重新收集统计。

JSON_TABLE 适合把文档展开成关系结果做报表和接口转换,不建议在高频事务里对大数组实时展开。
JSON 类型写入即校验,格式错误直接拒绝,错误码 -3105,数据入口的格式约束可以下沉到数据库。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服