朋友们好久不见!!
在数据仓库和实时分析场景里,OLAP 存储选型往往决定了后续查询性能、数据更新成本和运维复杂度。之前在 京东科技 做数据仓库相关工作时,我们主要使用 ClickHouse 来支持数据的快速读取。当时的链路比较典型:离线数据通过数据仓库加工完成后,直接导入到 ClickHouse,供报表、看板和分析系统消费。ClickHouse 在宽表聚合、日志类查询、大表扫描场景下表现很稳,但对频繁更新、部分列更新、多表关联、实时 CDC 同步这类场景,使用起来会相对重一些。
现在 Keep 做数据分析与数据仓库相关工作时,我们更多转向了 StarRocks 。实时链路主要通过 Flink 把数据写入 StarRocks,部分需要快速响应报表查询的数据,也会通过 LOAD 命令 定时刷新。这个过程中最直观的感受是:StarRocks 对实时写入、主键更新、部分列更新、即席查询和 BI 报表的支持更友好,尤其适合"既有实时写入,又有复杂查询"的业务场景。
StarRocks 和 ClickHouse 的主要差异
ClickHouse 更适合 只追加、宽表聚合、日志类分析 ;StarRocks 则在 实时更新、主键模型、多表 JOIN、高并发 BI、物化视图自动改写 上更有优势。
简单对比:
|---------|------------|------------------------|
| 维度 | ClickHouse | StarRocks |
| 典型场景 | 日志、埋点、宽表聚合 | 实时数仓、BI 报表、即席分析 |
| 数据更新 | 支持但相对重 | 主键模型天然支持更新 |
| 多表 JOIN | 可以,但对设计依赖强 | CBO + 向量化引擎表现更稳 |
| 实时写入 | 适合批量追加 | 适合 Flink/CDC/LOAD 实时写入 |
| BI 查询 | 可用,但并发需优化 | MySQL 协议兼容,BI 接入方便 |
| 物化视图 | 需要较多人工维护 | 支持查询自动路由 |
所以,如果数据主要是"写一次、查很多次",ClickHouse 仍然很有竞争力;但如果数据需要频繁更新、需要承接实时 CDC、需要支持多表关联和高并发报表,StarRocks 会更容易落地。
StarRocks 三种表模型:明细、聚合、主键
StarRocks 的表模型本质上回答一个问题:当相同 Key 的数据多次写入时,系统应该怎么处理?
|--------------------|------------------------------------|--------------------|
| 表模型 | 写入行为 | 适合场景 |
| 明细模型 Duplicate Key | 相同 Key 也保留,不合并、不覆盖 | 日志、行为事件、流水明细 |
| 聚合模型 Aggregate Key | 相同 Key 按 SUM/MAX/MIN/REPLACE 等函数合并 | 报表预聚合、指标汇总 |
| 主键模型 Primary Key | 相同主键保留最新一行,支持更新/删除/部分列更新 | 订单状态、用户画像、CDC 实时同步 |
明细模型:保留每一次写入
明细模型对应 DUPLICATE KEY。它的特点是:不强制唯一,不自动合并,不自动覆盖。即使两条数据的 Key 完全相同,也会同时保留。
它适合保存:
用户行为日志;
操作流水;
事件明细;
需要保留历史变化的原始数据;
不需要预聚合、也不希望丢失明细的场景。
比如在 Keep 的场景中,如果我们要保留用户每次运动打卡、每次页面点击、每次设备上报的原始事件,就可以使用明细模型。查询时如果需要"最新状态",就要通过 SQL 自己取最新一条;但如果业务本身就关心过程数据,明细模型非常合适。
示例建表:
sql
CREATE TABLE user_event_log (
event_time DATETIME NOT NULL,
user_id BIGINT NOT NULL,
event_type VARCHAR(32) NOT NULL,
event_page VARCHAR(64),
device_id VARCHAR(64)
)
DUPLICATE KEY(event_time, user_id, event_type)
DISTRIBUTED BY HASH(user_id)
PROPERTIES (
"replication_num" = "3"
);
聚合模型:写入时就合并
聚合模型对应 AGGREGATE KEY。它的特点是:相同聚合键的数据,在导入、Compaction 和查询阶段会按指定聚合函数合并。
常见聚合函数包括:
● SUM
● MAX
● MIN
● REPLACE
● BITMAP_UNION
● HLL_UNION
它适合保存:
● 每日站点访问量;
● 城市维度销售汇总;
● 商品维度订单统计;
● 报表层预聚合指标;
● 查询模式相对固定、不需要还原原始明细的场景。
聚合模型的优势是查询快,因为数据已经提前合并;代价是原始明细会丢失,不能从这张表反推出每一条原始记录。
示例建表:
sql
CREATE TABLE site_visit_agg (
site_id INT NOT NULL,
city_code SMALLINT NOT NULL,
visit_date DATE NOT NULL,
pv BIGINT SUM DEFAULT '0',
uv BIGINT HLL_UNION
)
AGGREGATE KEY(site_id, city_code, visit_date)
DISTRIBUTED BY HASH(site_id)
PROPERTIES (
"replication_num" = "3"
);
主键模型:保留最新状态,适合实时更新
主键模型对应 PRIMARY KEY。它的特点是:主键唯一且非空,相同主键的新数据会替代旧数据。相比早期的 Unique Key 模型,主键模型采用更高效的 Merge-on-Write 机制,查询时不需要像聚合模型那样做大量 Merge 操作,因此在实时更新场景下查询性能更好。
它适合:
● 订单当前状态;
● 用户最新画像;
● 商品库存;
● 从 MySQL 等业务库通过 CDC 同步的实时数据;
● 需要 UPDATE、DELETE、部分列更新的场景。
在 Keep 的实时链路中,如果订单状态、会员等级、运动计划状态会不断变化,用主键模型会比明细模型更自然。Flink 把变更数据写入 StarRocks 后,查询端直接拿到最新状态即可。
示例建表:
sql
CREATE TABLE user_profile_pk (
user_id BIGINT NOT NULL,
nick_name VARCHAR(64),
level INT,
city VARCHAR(32),
last_login DATETIME,
update_time DATETIME)
PRIMARY KEY(user_id)
DISTRIBUTED BY HASH(user_id)
PROPERTIES (
"replication_num" = "3"
);
三种模型怎么选
|---------------------|------|
| 判断问题 | 推荐模型 |
| 数据是否需要保留每一次写入? | 明细模型 |
| 是否只需要汇总结果,不需要原始明细? | 聚合模型 |
| 数据是否会频繁更新、删除、部分列变更? | 主键模型 |
| 是否来自 MySQL/业务库 CDC? | 主键模型 |
| 是否主要做日志、事件、流水分析? | 明细模型 |
| 是否主要做固定维度报表加速? | 聚合模型 |
| 是否需要实时看到"最新一行状态"? | 主键模型 |
结合真实链路的实践建议
在 ClickHouse 为主的离线链路里,我们通常更关注"数据导入后查询够不够快"。而在 StarRocks 为主的实时链路里,除了查询性能,还要考虑 数据更新方式、写入频率、主键设计、部分列更新、LOAD 任务稳定性。
如果从 Flink 实时写入,建议优先考虑主键模型; 如果从离线数仓批量导入报表层数据,聚合模型往往能显著降低查询开销; 如果保存的是原始事件、日志、行为流水,明细模型最稳妥。
表模型一旦创建后不能直接修改,所以建表前最好结合真实查询 SQL、写入频率、主键基数、更新方式做一次小规模验证。对于数据分析、数据仓库和实时 BI 场景来说,StarRocks 的价值不只是"查询快",更在于它能用一套系统同时覆盖 明细查询、预聚合加速、实时更新和高并发报表。
附:Flink 写入 StarRocks 与 LOAD 刷新示例
1. Flink SQL 写入 StarRocks
下面是一个基于 Flink SQL 的最小示例:把 MySQL CDC 变更实时同步到 StarRocks 主键表。StarRocks Connector 内部会把数据批量通过 Stream Load 写入 StarRocks。
sql
-- MySQL CDC 源表
CREATE TABLE mysql_orders (
id BIGINT,
user_id BIGINT,
amount DECIMAL(10,2),
status STRING,
update_time TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
'hostname' = 'mysql-host',
'port' = '3300',
'username' = 'flink_user',
'password' = 'flink_pass',
'database-name' = 'shop',
'table-name' = 'orders',
'server-time-zone' = 'Asia/Shanghai',
'scan.startup.mode' = 'initial'
);
-- StarRocks 目标表
CREATE TABLE sr_orders (
id BIGINT,
user_id BIGINT,
amount DECIMAL(10,2),
status STRING,
update_time TIMESTAMP(3),
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'starrocks',
'jdbc-url' = 'jdbc:mysql://sr-fe:9030',
'load-url' = 'sr-fe:8030',
'database-name' = 'shop',
'table-name' = 'orders',
'username' = 'root',
'password' = '',
'sink.buffer-flush.interval-ms' = '5000',
'sink.properties.format' = 'json',
'sink.properties.strip_outer_array' = 'true',
'sink.semantic' = 'exactly-once'
);
-- 实时同步
INSERT INTO sr_orders
SELECT id, user_id, amount, status, update_time
FROM mysql_orders;
这里需要注意:CDC 同步到 StarRocks 时,目标表通常应使用主键模型,否则源端 DELETE/UPDATE 变更无法正确同步。
2. Stream Load:适合脚本、离线批量刷新
Stream Load 是 StarRocks 基于 HTTP 的同步导入方式,适合从本地文件、脚本或调度任务中刷新数据。
bash
curl --location-trusted \
-u root: \
-H "label: report_20261022" \
-H "column_separator:," \
-H "format: csv" \
-T ./daily_report.csv \
http://sr-fe:8030/api/shop/daily_report/_stream_load
如果是 JSON 格式,可以加上:
bash
-H "format: json" \
-H "strip_outer_array: true"
Stream Load 会同步返回导入结果,适合单次数据刷新、报表补数、离线数仓结果表导入等场景。
3. Broker Load:适合从 HDFS / 对象存储导入大文件
如果数据已经在 HDFS、OSS、S3 等外部存储上,建议使用 Broker Load。它是异步导入方式,适合几十 GB 到上百 GB 级别的数据文件。
bash
LOAD LABEL shop.label_daily_report_20261022
(
DATA INFILE("hdfs://namenode:8020/warehouse/daily_report/2026-10-22/*.csv")
INTO TABLE daily_report
COLUMNS TERMINATED BY ","
(id, user_id, amount, status, update_time)
)
WITH BROKER "broker_name"
PROPERTIES
(
"timeout" = "3600"
);
导入完成后,可以通过 information_schema.loads 查看任务状态。
4. 三种导入方式怎么选
|-----------------|-------------------------------------|-------------------------|
| 方式 | 特点 | 适用场景 |
| Flink Connector | 实时、支持 Exactly-once、自动批量 Stream Load | 实时 CDC、Kafka 流、秒级到分钟级同步 |
| Stream Load | 同步、HTTP 接口、脚本友好 | 离线报表刷新、小批量补数、调度任务 |
| Broker Load | 异步、适合大文件、支持 HDFS/对象存储 | 离线数仓批量导入、历史数据迁移 |
在实际项目里,实时链路优先用 Flink Connector,离线报表刷新优先用 Stream Load,大规模历史数据导入优先用 Broker Load。如果表模型是主键模型,Flink Connector 和 Stream Load 都能天然支持更新;聚合模型则会在导入阶段按聚合函数合并;明细模型则保留每一次写入。