

导读:StarRocks 4.0 已正式发布!这一版本带来了多项关键升级。本篇聚焦 JSON 查询性能的系统性提升——通过全新的 FlatJSON 列式存储与执行优化机制,StarRocks 4.0 让 JSON 在实时分析场景中具备接近原生列存的性能。无论是日志、埋点还是 IoT 数据,用户都无需额外 ETL,即可直接对 JSON 进行高性能查询分析,真正让“灵活的数据结构”与“高效的分析能力”兼得。
在实时分析场景中,日志、点击流、埋点、用户画像等数据几乎无处不在。这些数据通常以 JSON 格式存储——它灵活、通用、无需建模,尤其适合快速变化的业务场景:字段可以随时新增或删除,系统之间也能无障碍传输。
正因如此,JSON 成为互联网业务中最常见的数据格式。然而,当这种灵活的数据进入数据库,情况就截然不同了。
在日志分析或行为分析场景中,即便 SQL 写得没问题,查询仍可能迟迟跑不出来。
SELECT
get_json_string(event, '$.type') AS event_type,
COUNT(DISTINCT user_id)
FROM events_log
WHERE
get_json_string(event, '$.region') = 'US' AND
to_datetime(get_json_int(dt, '$.event_ts')) BETWEEN '2024-01-01' AND '2024-12-31'
GROUP BY event_type;这条简单的 SQL 为什么这么慢?因为在数据库眼里,JSON 就是一块黑盒:
看似只是过滤 region='US'、分组 event_type 并做一次去重计数,但执行时间可能长达几十秒。CPU 飙升、延迟高企——这并非 SQL 写得复杂,而是因为 JSON 最初并非为分析而设计的。
当 JSON 成为分析的基础时,它的优势很快就会变成劣势:
多年来,工程师一直在尝试各种解决思路,大体可以分为三类:
ALTER TABLE tbl ADD COLUMN json_event_type STRING AS get_json_string(event, '$.event_type')不过,这种方案的实现极具挑战——系统需要智能地处理异构 schema、 schema 演进等复杂问题。因此,各数据库产品在这一方向的优化深度差异很大。

为了解决这些问题,StarRocks 在 2.3 版本支持了 Binary JSON,即以二进制序列化格式来存储 JSON,规避了每次解析字符串的开销,并且搜索字段时能够应用二分查找来降低 CPU 开销。但是即便如此,总体仍然是非常低效,以文首查询为例:
get_json_*,解析 region、 event_type、 user_idregion='US' 需要逐字符比较,CPU 热点函数可能 80-90% 都耗在解析和比较上哪怕只有几千万行数据,这样的查询依旧可能需要几十秒。
在持续优化 JSON 查询性能的过程中,StarRocks 逐步实现了从“字符串存储”到“列式存储”的演进:
在深入技术细节之前,可以先直观感受不同方案带来的性能差异。以文首相同的查询为例,不同版本的 JSON 实现具有显著的性能差异:

接下来,本文将从技术原理角度展开介绍 FlatJSON 如何实现“既灵活又高效”的存储与查询能力。

要理解 FlatJSON 的设计原理,首先需要回顾 StarRocks 的基础存储结构:
基于这一架构,FlatJSON 在数据导入阶段进行列化存储:
经过这一过程,原本需要层层解析的 JSON 数据,在物理存储上被转化为一张“半结构化表”。
在 Segment 文件内部,这些字段已经与普通列无异——可被按需读取,也能创建索引。
为什么 FlatJSON 更快?
SELECT
get_json_string(event, '$.type') AS event_type,
COUNT(DISTINCT user_id)
FROM events_log
WHERE
to_datetime(get_json_int(dt, '$.event_ts'))
BETWEEN '2024-01-01' AND '2024-12-31'
AND get_json_string(event, '$.region') = 'US'
GROUP BY event_type;回到文首的示例查询:
得益于这种列式化设计,JSON 性能有了质的飞跃!
在存储实现列化之后,执行层的优化同样关键。以下将介绍 FlatJSON 在执行阶段的四项核心技术,以及它们如何进一步提升查询性能。

为避免全表扫描,FlatJSON 支持在已列化字段(如 region、event_time、event_type)上创建合适的索引,例如 ZoneMap。ZoneMap 会记录每个 Page 的最小值和最大值,在查询时可据此快速判断哪些 Page 可能命中过滤条件,从而跳过无关数据。
性能提升的核心来自于扫描量的降低:

对于低基数字符串字段,Segment 在写入时会自动进行字典编码。以 region 字段为例,系统会将其拆分为两部分存储:字典和字典码,从而消除重复字符串。
在查询执行阶段,StarRocks 会对字典编码数据进行谓词改写:例如 region='US' 会被改写为 region=1,然后利用索引直接过滤对应的数据块。这种方式避免了频繁的字符串比较,计算更轻量,内存与 I/O 效率均明显提升。
在完成过滤后,系统再将字典码翻译回实际字符串,用于计算。

在传统的执行路径中,数据库通常会在过滤之前就将整行数据解码。这意味着,即使最终被过滤掉的记录,也已经付出了完整的解析与对象创建成本。
延迟物化的思路正好相反:在初始阶段仅使用轻量级的数据结构(例如行号)来表示记录,在完成所有过滤操作之后,再根据筛选结果去读取真正需要的列。以 region='US' 为例,StarRocks 会先读取并过滤 region 字段,仅记录满足条件的行号。随后再按照这些行号读取 user_id 字段,从而避免无谓的数据加载。
延迟物化的核心在于延后不必要的计算与解码:它减少了 I/O 读量,提高了缓存命中率,并让 CPU 资源集中用于真正需要的数据。
因此,在低选择度的查询中,这一机制能显著降低无效计算成本。

在 StarRocks 中,字符串一直是影响查询性能的关键因素。无论是过滤还是聚合,都需要进行逐字符比较或字符串哈希,不仅消耗大量 CPU,还容易破坏缓存局部性。为此,StarRocks 在 Segment 局部字典的基础上引入了 全局字典,通过汇总各节点的局部字典,构建出统一的全局映射表。
有了全局字典,字典编码的使用范围不再局限于单个 Segment,而可扩展到 聚合、排序、关联等计算场景。原本昂贵的字符串运算被“降维”为整数运算:CPU 只需执行轻量的整型比较或哈希操作,缓存命中率显著提升,哈希表也更紧凑、更高效,聚合时的冲突率大幅降低。
例如,原本的 GROUP BY region 操作需要在哈希表中频繁进行字符串查找和更新;使用全局字典后,执行计划可改写为 GROUP BY region_code,以整数完成聚合,仅在最终输出阶段再将整数解码为字符串,从而显著减少计算开销。

回到文首的示例,可以直观看出两种执行方式的差异:
相比传统方式,FlatJSON 在执行路径上大幅减少了解析、比较和全量扫描等开销。
在实际测试中,对 10 亿行 JSON 数据执行相同的聚合查询,传统方式可能耗时约 30 秒,
而基于 FlatJSON 的查询仅需 约 500 毫秒,I/O 与 CPU 成本均降低了数个数量级。
在真实的业务场景中,FlatJSON 的价值不仅体现在性能提升上,更体现在对灵活性与易用性的平衡。
痛点:实时看板需要对最新埋点数据进行聚合和分组,通常涉及数十亿行 JSON。传统模式下,查询需要逐行解析 JSON,返回结果往往需要数十秒甚至更久;同时,埋点字段变化频繁,新字段出现时必须修改 ETL 流程。
FlatJSON 方案:在数据导入阶段,系统会自动将常用字段(如 user_id、event_time、region、event_type 等)拍平成列存格式。新字段出现时,不会影响现有查询,用户仍可通过 JSON 函数直接访问,系统会根据访问频率判断是否列化。
收益:查询延迟从数十秒降至数百毫秒;同时兼容频繁变化的埋点 schema,分析团队无需因字段变更频繁修改表结构或 ETL 流程。
痛点:电商订单数据通常以 JSON 形式存储,包含 SKU、价格、促销、地域、配送等上百个字段。传统方案依赖复杂的 ETL 流程来抽取字段,开发与维护成本高;当字段新增或类型不一致(例如 price 既可能是字符串也可能是数值)时,整个数据流水线容易出错。
FlatJSON 方案:在导入阶段,FlatJSON 自动识别并列化高频字段,同时保留完整的 Binary JSON 结构。即使 schema 动态变化或字段类型异构,查询仍可正常执行,常用字段的访问性能依旧接近原生列存。
收益:报表生成时间从分钟级缩短至秒级,开发和运维团队无需频繁调整 ETL 流程,分析团队能更快速地响应业务变化。
痛点:IoT 设备上报的数据通常以 JSON 格式存储,包含数十个指标,不同型号设备间的字段差异显著(异构 schema 十分常见)。传统模式下,数据库需要完整解析 JSON;若字段缺失或类型不一致,查询容易退化甚至报错。
FlatJSON 方案:FlatJSON 会在导入阶段自动将高频指标(如温度、湿度等)列化存储,其余字段保留在 Binary JSON 中。查询时系统能自动跳过不存在的字段,避免执行错误;对于类型不一致的字段,执行层可进行统一处理或延迟物化。
收益:支持千万级设备数据的实时聚合,能够在秒级生成监控报表。同时,IoT 场景中的动态与异构 schema 由系统自动处理,显著减少人工清洗和建模成本。
FlatJSON 不仅解决了性能问题,也让动态 schema 与异构 schema 成为可控的系统成本。对开发者而言,不必再因字段新增、缺失或类型不一致而焦虑;对分析师而言,依旧可以通过 SQL 自由查询,而无需担心底层数据结构的频繁变化。
FlatJSON 的启用方式非常简单,使用方法与标准 JSON 类型几乎相同。
-- Minimal table with a JSON column and FlatJSON enabled
CREATE TABLE events_log (
dt DATE,
event_id BIGINT,
event JSON
)
DUPLICATE KEY(`dt`, event_id)
PARTITION BY date_trunc('DAY', dt)
DISTRIBUTED BY HASH(dt, event_id)
PROPERTIES (
"flat_json.enable" = "true", -- enable FlatJSON for this table
"flat_json.null.factor" = "0.3" -- optional: skip extracting too-sparse fields
);可以通过表属性来开启或关闭 FlatJSON;新导入的 JSON 数据会自动被列化存储。
此外,还可以通过参数 flat_json.null.factor 设置阈值,以避免抽取过于稀疏的字段。
插入几行示例数据
INSERT INTO events_log VALUES
('2025-09-01', 1001, PARSE_JSON('{
"user_id": 12345, "region": "US", "event_type": "click", "ts": 1710000000
}')),
('2025-09-01', 1002, PARSE_JSON('{
"user_id": 54321, "region": "CA", "event_type": "purchase", "ts": 1710000300,
"experiment_flag": "A" -- rare field, remains in JSON fallback
}'));像平常一样使用 JSON 函数进行查询。
-- Filter + group on fields that FlatJSON likely extracted (high-occurrence)
SELECT
get_json_string(event, '$.event_type') AS event_type,
COUNT(*) AS cnt
FROM events_log
WHERE
get_json_string(event, '$.region') = 'US'
AND get_json_int(event, '$.ts') BETWEEN 1710000000 AND 1710003600
GROUP BY event_type;FlatJSON 为 JSON 在数据库中的处理提供了一种工程化的解决路径。在存储层,FlatJSON 通过自动列化高频字段,提高压缩率并显著降低扫描成本;在执行层,则结合索引、全局字典和延迟物化等优化技术,减少了解析与解码开销。实际测试表明,在相同 SQL 下,查询延迟可从数十秒下降至亚秒级。
对于用户来说,无需再在“灵活性”和“性能”之间反复取舍。埋点、日志、IoT 等动态 schema 的数据可以直接写入 StarRocks,既能支持快速变化的业务需求,又能提供稳定可预期的查询性能,从而降低建模和 ETL 成本,让团队更专注于业务分析本身。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。