0x00 概要
- StarRocks 相关文档
- clickhouse 相关文档
- 对该功能有如下几个疑问
- 存储空间相比之前存储 json 的变化【本文未验证】
- 导入效率慢多少【本文未验证】
- 查询效率提升多少【本文未验证】
- 单个列 A 有 varchar 和 int 时,按什么推算?
- insert into 写入的时候是生效?
- NULL 值推算
- 如何查看行 json 数据推算效果
- 二级以上深度的情况下,写放大增加多少?【本文未验证】
StarRocks 测试
总结:
- 插入数据量非常少,无法测试性能,从功能行为逻辑上可有效提升部分列查询时的效率、谓词过滤能力、避免无效数据参与计算与过滤
- 边界测试
k2->'null' = "null"
时,在 json 行为上获取一个不存在的 key 时,会自动返回一个 null;此时在数据库判定时: null is null 会成为 1=1 的效果
测试过程
1
2
3
4
5
| 查看该功能是否为 true
select * from information_schema.be_configs where name = "enable_json_flat";
动态开启该功能
update information_schema.be_configs set VALUE = "true" where name = "enable_json_flat";
|
1
2
3
4
5
6
7
8
9
10
| CREATE TABLE `t1` (
`k1` int,
`k2` JSON,
`k3` VARCHAR(20),
`k4` JSON
)
DUPLICATE KEY(`k1`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`k1`) BUCKETS 1
PROPERTIES ("replication_num" = "1");
|
- 先写入一行数据,查看对应
FROM t1[_META_];
- 数据默认只存储物化后的内容,原始数据不会存储;查询的时候重新拼接成 json 数据
- meta 信息列会跟随数据量、数据内容的变化而不断的推导
1
2
| INSERT INTO t1 (k1,k2) VALUES
(15,parse_json('{"str":"test_str0","Integer":11,"Double":3.14,"Object":{"a":"b"},"arr":[1,2,3],"Bool":true,"null":null}'));
|
1
2
3
| 查看对应 FROM t1[_META_];
["nulls(TINYINT)","Bool(JSON)","Double(DOUBLE)","Integer(BIGINT)","Object.a(VARCHAR)","arr(JSON)","null(JSON)","str(VARCHAR)"]
|
tips:
当数据 compact 后,默认将占有达到 90% 的相同数据做物化并做 meta 信息展示
在没有 compaction 且数据量较少( 测试环境是 10 条以内)时,FROM t1[_META_];
可能会返回多条信息
- 更换内容再写入一行数据,查看对应
FROM t1[_META_];
1
2
| INSERT INTO t1 (k1,k2) VALUES
(15,parse_json('{"abc":"test_str0","abd":11,"abf":3.14,"abh":{"a":"b"},"abr":[1,2,3],"abb":true,"abn":null}'));
|
1
2
3
| 查看对应 FROM t1[_META_];(截取了 1 行,此时返回 2 行数据)
["nulls(TINYINT)","abb(JSON)","abc(VARCHAR)","abd(BIGINT)","abf(DOUBLE)","abh.a(VARCHAR)","abn(JSON)","abr(JSON)"]
|
- 更换内容后,再写入一行数据,查看对应
FROM t1[_META_];
- 更换 key name、修改 value type
- meta 返回为空(此时 3 条数据,不满足 flat json 生成 meta 的阈值,没有建立物化 json 的 meta 信息)
1
2
3
4
5
6
7
8
9
10
| INSERT INTO t1 (k1,k2) VALUES
(15,parse_json('{"abc":"test_str0","abd":"gooo","abf":"happy","abh":[1,2,3],"abr":true,"abb":123,"abn":null}'));
mysql> SELECT flat_json_meta(k2) FROM t1[_META_];
+--------------------+
| flat_json_meta(k2) |
+--------------------+
| [] |
+--------------------+
1 row in set (0.03 sec)
|
- 插入一个不合法的数据(与上述问题相同,实则与数据本身无关),数据在写入过程中转为了 NULL 然后插入(没有开启严格模式 Strict Mode)
- column 为 NULL 的时候,同样会被 flat json 所计算,会被列入
"nulls(TINYINT)"
meta 信息内(当 key 列的数据为 None 或者 null 时也会列入该数据列 - 错误原因:value 是字符串的情况下,没有双引号
"abd":gooo,"abf":happy,
导致 json 格式不合法,开启严格模式事物会提交失败
1
2
3
4
5
6
7
| INSERT INTO t1 (k1,k2) VALUES
(150,parse_json('{"abc":"test_str0","abd":gooo,"abf":happy,"abh":[1,2,3],"abr":true,"abb":123,"abn":null}'));
Query OK, 1 row affected (0.12 sec)
{'label':'insert_e2b25886-a265-11ef-93ed-02427b1a17fc', 'status':'VISIBLE', 'txnId':'13'}
查看对应 FROM t1[_META_];
[]
|
- 当数据无法被推算的时候,使用 json 行为作为兜底策略
- clickhouse 使用 Variant 作为数据列推算
- 当 select 返回数据的时候,explain verbose 里看到数据列为 json
1
2
3
4
5
6
7
| mysql> SELECT flat_json_meta(k2) FROM t1[_META_];
+--------------------------------------------------------------------------------------------------------------+
| flat_json_meta(k2) |
+--------------------------------------------------------------------------------------------------------------+
| ["nulls(TINYINT)","abb(JSON)","abc(VARCHAR)","abd(JSON)","abf(JSON)","abn(JSON)","abr(JSON)","remain(JSON)"] |
+--------------------------------------------------------------------------------------------------------------+
1 row in set (0.05 sec)
|
select from t1[_META_]
语法边界
FROM t1[_META_];
本身是把指定列的 json meta 信息打印,正常为 1 条(相同 kv 格式化使用同一套)- 不支持 where 条件选择
- 不支持 order by
- 支持 limit 1 返回行数
- 刚导入了数据后,
FROM t1[_META_];
可能会返回多行信息- 快速执行手动 compact 后,会留下一条信息
ALTER TABLE t1 COMPACT;
ALTER TABLE t1 BASE COMPACT;
1
2
3
4
5
6
7
8
9
10
| -- 验证 where 是否参与过滤行为
mysql> SELECT flat_json_meta(k2) FROM t1[_META_] where k1 != 15 ;
+--------------------------------------------------------------------------------------------------------------------------------+
| flat_json_meta(k2) |
+--------------------------------------------------------------------------------------------------------------------------------+
| ["nulls(TINYINT)","abb(JSON)","abc(VARCHAR)","abd(BIGINT)","abf(DOUBLE)","abh.a(VARCHAR)","abn(JSON)","abr(JSON)"] |
| [] |
| ["nulls(TINYINT)","Bool(JSON)","Double(DOUBLE)","Integer(BIGINT)","Object.a(VARCHAR)","arr(JSON)","null(JSON)","str(VARCHAR)"] |
+--------------------------------------------------------------------------------------------------------------------------------+
3 rows in set (0.05 sec)
|
- 默认返回 3 条数据,使用 limit 1 语法返回 1 条数据
1
2
3
4
5
6
7
| mysql> SELECT flat_json_meta(k2) FROM t1[_META_] limit 1;
+--------------------------------------------------------------------------------------------------------------------------------+
| flat_json_meta(k2) |
+--------------------------------------------------------------------------------------------------------------------------------+
| ["nulls(TINYINT)","Bool(JSON)","Double(DOUBLE)","Integer(BIGINT)","Object.a(VARCHAR)","arr(JSON)","null(JSON)","str(VARCHAR)"] |
+--------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.04 sec)
|
flat json 相关参数
- 控制 flat json 按什么范围的数据生产 meta 信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| enable_json_flat
默认值:false
类型:Boolean
是否动态:是
描述:是否开启 Flat JSON 特性。开启后新导入的 JSON 数据会自动打平,提升 JSON 数据查询性能。
引入版本:v3.3.0
json_flat_null_factor
默认值:0.3
类型:Double
是否动态:是
描述:控制 Flat JSON 时,提取列的 NULL 值占比阈值,高于该比例不对该列进行提取,默认为 0.3。该参数仅在 enable_json_flat 为 true 时生效。
引入版本:v3.3.0
json_flat_sparsity_factor
默认值:0.9
类型:Double
是否动态:是
描述:控制 Flat JSON 时,同名列的占比阈值,当同名列占比低于该值时不进行提取,默认为 0.9。该参数仅在 enable_json_flat 为 true 时生效。
引入版本:v3.3.0
json_flat_column_max
默认值:100
类型:Int
是否动态:是
描述:控制 Flat JSON 时,最多提取的子列数量。该参数仅在 enable_json_flat 为 true 时生效。
引入版本:v3.3.0
enable_compaction_flat_json
默认值:True
类型:Bool
是否动态:是
描述:控制是否为 Flat Json 数据进行 Compaction。
引入版本:v3.3.3
enable_lazy_dynamic_flat_json
默认值:True
类型:Bool
是否动态:是
描述:当查询在读过程中未命中 Flat JSON Schema 时,是否启用 Lazy Dynamic Flat JSON。当此项设置为 true 时,StarRocks 将把 Flat JSON 操作推迟到计算流程,而不是读取流程。
引入版本:v3.3.3
|
StarRocks 查询验证
flat json 在物化 key / values 时,数据是通过 cast 函数将 json kv 转为 int、varchar、decimal 等行为存储到 BE 节点,当单个 column 有多种数据格式会使用 json 行为存储(保底行为)
需要注意数据写入、查询 后行为的一致性,针对 bool、tinyint、38 位以上的 浮点数、None / NULL 等数据边界一定要注意写入和查询时的一致性(建议自行验证是否符合业务逻辑)
1
2
3
4
5
6
7
| mysql> SELECT get_json_string(k2,'\$.abf') FROM t1 WHERE k2->'abb' = 123;
+------------------------------+
| get_json_string(k2, '$.abf') |
+------------------------------+
| happy |
+------------------------------+
1 row in set (0.04 sec)
|
Profile 分析是否命中 flat json
从 profile 上可以看到 json kv 在字段谓词过滤时 hit / unhit 的行为
- AccessPathHits
- Hit:命中 meta 后加速的数据
- HitMerge:数据存在多个版本(比如导入数据后,没有 compaction)
- hit 和 HitMerge 在查询 1 个 column 时二选一;目前从测试上未见同时出现常见
- AccessPathUnhits
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| UniqueMetrics:
- MorselQueueType: fixed_morsel_queue
- Predicates: json_query(2: k2, 'Double') = CAST(3.14 AS JSON)
- Rollup: t1
- SharedScan: False
- Table: t1
- AccessPathHits: 3
- [Hit]Double: 1
- [Hit]Object.a: 1
- [Hit]remain: 1
- AccessPathUnhits: 2
- [Unhit]Double: 1
- [Unhit]Object: 1
- PushdownAccessPaths: 2
- PushdownPredicates: 1
|
当大量数据写入后未 compaction 时,会有 HitMerge 的信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| UniqueMetrics:
- MorselQueueType: fixed_morsel_queue
- Predicates: json_query(2: k2, 'Double') = CAST(3.14 AS JSON)
- Rollup: t1
- SharedScan: False
- Table: t1
- AccessPathHits: 7
- [HitMerge]abb: 1
- [HitMerge]abc: 1
- [HitMerge]abd: 1
- [HitMerge]abf: 1
- [HitMerge]abh.a: 1
- [HitMerge]abn: 1
- [HitMerge]abr: 1
- AccessPathUnhits: 0
|
Json 中的 None 与 字符串 null 、SQL NULL
1
2
3
4
5
| explain verbose SELECT get_json_string(k2,'\$.abf') FROM t1 WHERE k2->'null' = "null";
-- 截取重要信息列如下
5 <-> get_json_string[([2: k2, JSON, true], '$.abf'); args: JSON,VARCHAR; result: VARCHAR; args nullable: true; result nullable: true]
Predicates: json_query[([2: k2, JSON, true], 'null'); args: JSON,VARCHAR; result: JSON; args nullable: true; result nullable: true] = cast('null' as JSON)
|
- 测试如下三种场景
- key 信息,value 为 SQL NULL
- 不存在的 key 信息,json 库自动转为 null
- 获取存在的 key 信息,value 为 null (等效一个 string 字符)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // 获取存在的 key 信息,value 为 null( 等效 MySQL NULL)
mysql> select parse_json('{"a": null}')->"a", parse_json('{"a": null}')->"b" is null;
+--------------------------------+------------------------------------------+
| parse_json('{"a": null}')->'a' | (parse_json('{"a": null}')->'b') IS NULL |
+--------------------------------+------------------------------------------+
| null | 1 |
+--------------------------------+------------------------------------------+
1 row in set (0.01 sec)
// 获取不存在的 key 信息,json 库自动转为 null
mysql> select parse_json('{"a": null}')->"b", parse_json('{"a": null}')->"b" is null;
+--------------------------------+------------------------------------------+
| parse_json('{"a": null}')->'b' | (parse_json('{"a": null}')->'b') IS NULL |
+--------------------------------+------------------------------------------+
| NULL | 1 |
+--------------------------------+------------------------------------------+
1 row in set (0.02 sec)
// 获取存在的 key 信息,value 为 null (等效一个 string 字符)
mysql> select parse_json('{"a": "null"}')->"a", parse_json('{"a": "null"}')->"a" is null;
+----------------------------------+--------------------------------------------+
| parse_json('{"a": "null"}')->'a' | (parse_json('{"a": "null"}')->'a') IS NULL |
+----------------------------------+--------------------------------------------+
| "null" | 0 |
+----------------------------------+--------------------------------------------+
1 row in set (0.02 sec)
|
Tips:使用 explain verbose SELECT
检查 SQL 语句在执行过程中使用的 column type;方便判断数据在 cast 转换后是否符合预期中的数学加工逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| mysql> explain verbose select k2->'null' from t1;
+------------------------------------------------------------------------------------------------------------------------------------+
| Explain String |
+------------------------------------------------------------------------------------------------------------------------------------+
| RESOURCE GROUP: default_wg |
| |
| PLAN COST |
| CPU: 57344.0 |
| Memory: 0.0 |
| |
| PLAN FRAGMENT 0(F00) |
| Fragment Cost: 28672.0 |
| Output Exprs:5: expr |
| Input Partition: RANDOM |
| RESULT SINK |
| |
| 1:Project |
| | output columns: |
| | 5 <-> json_query[([2: k2, JSON, true], 'null'); args: JSON,VARCHAR; result: JSON; args nullable: true; result nullable: true] |
| | cardinality: 28 |
| | |
| 0:OlapScanNode |
| table: t1, rollup: t1 |
| preAggregation: on |
| partitionsRatio=1/1, tabletsRatio=1/1 |
| tabletList=10165 |
| actualRows=28, avgRowSize=1025.0 |
| ColumnAccessPath: [/k2/null(json)] |
| cardinality: 28 |
+------------------------------------------------------------------------------------------------------------------------------------+
25 rows in set (0.03 sec)
|
Author
Atman
LastMod
2024-11-14
License
CC BY-NC-ND 4.0 | 本文不带任何官方色彩,最终解释权归本站所有