业务里半结构化的数据越来越多,配置项、标签、接口报文都不适合为每个字段建表。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 就是做这件事的。
代码 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 字段的过滤条件本质是函数表达式,默认不会走普通索引。构造一张 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)||'"]}'
  FROM SYSDUAL CONNECT BY ROWNUM<=500000;
COMMIT;
没有索引时,按 name 过滤是全表扫描,CSCN2 要扫完 50 万行。
代码 9 无索引时的执行计划(末位为扫描行数,代价 63)
EXPLAIN SELECT ID FROM T\_ORDER\_JSON
  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
  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
  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 |
说明:
50 万行短文档在本实例连续执行后会驻留缓冲池,毫秒计时差异很小甚至接近 0,不能作为性能依据。稳定的判据是执行计划的访问路径、扫描行数和逻辑读。计划中的 BLKUP2 是回表取 ID,若查询只返回索引键还能进一步减少回表。
结构固定、频繁参与连接和约束的数据,仍按关系表建模;JSON 列适合结构多变、以整文档读取为主的场景。
需要按 JSON 内字段高频过滤,就对该 JSON_VALUE 建函数索引,并在批量数据变化后重新收集统计。
JSON_TABLE 适合把文档展开成关系结果做报表和接口转换,不建议在高频事务里对大数组实时展开。
JSON 类型写入即校验,格式错误直接拒绝,错误码 -3105,数据入口的格式约束可以下沉到数据库。
文章
阅读量
获赞
