从 ClickHouse 到 StarRocks:我在真实业务中怎么选明细、聚合、主键模型

朋友们好久不见!!

在数据仓库和实时分析场景里,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 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 都能天然支持更新;聚合模型则会在导入阶段按聚合函数合并;明细模型则保留每一次写入。

相关推荐
zoyation1 小时前
HBase 2.5.16 单机部署
大数据·数据库·hbase
Pin_Qing1 小时前
缓存能活多久,现在是个要算的参数
java·大数据·开发语言·人工智能·程序人生
ManageEngineITSM2 小时前
ITSM系统如何与企业微信、钉钉、飞书集成?提升工单处理效率的方法
java·大数据·开发语言·钉钉·企业微信·变更管理
dyj0952 小时前
基于 DAMA 数据治理体系的高校数据治理方案
大数据·数据治理
starzy19904 小时前
KafkaProducer 源码深度剖析:batch 发送的条件
大数据·kafka
Data-Miner15 小时前
需要做财务数据分析,有什么好用的工具?从准备到出报告的实操指南
大数据·数据库·数据分析
阿里云大数据AI技术17 小时前
StarRocks × Paimon C++:加速湖上数据分析
大数据·人工智能
用户36105886261217 小时前
Flink 配置深度剖析:从配置加载到参数调优的完整指南
大数据·flink
ha_lydms17 小时前
Window Functions(窗口函数)
大数据·数据仓库·hadoop·spark·数据处理·dataworks·maxcompute