2. 明细模型(Duplicate Key Model):模型原理、适用场景、建表语法、数据写入与查询示例

2.1 模型原理:说白了就是“原样存储”

明细模型,英文叫 Duplicate Key Model。它的核心思想很简单——你写入什么,我就存什么。不做任何聚合,不做任何去重,也不做任何预计算。

我刚开始接触 StarRocks 时,觉得这模型太“笨”了。后来才发现,恰恰是这种“笨”,让它成了最灵活、最通用的模型。你想想看,如果数据本身就不需要聚合,或者你还没想好要怎么分析,那明细模型就是最安全的选择。

它的底层原理是这样的:

  • 每一行数据都是一个独立的记录
  • 用户指定的 Duplicate Key 只用于排序,不用于去重
  • 同一批次或不同批次写入的完全相同的行,都会被保留
  • 数据按照 Duplicate Key 的顺序在磁盘上存储,这叫“前缀索引”

关键区别:明细模型没有“主键”概念。你指定的那些列,只是“排序列”,不是“唯一标识”。这是它和主键模型(Primary Key Model)最本质的区别。

我画了一张图,帮你理解明细模型的数据流向:

明细模型(Duplicate Key Model)数据流向图 数据源 写入请求 Duplicate Key 模型 不做去重 · 不做聚合 磁盘存储 按 Key 排序存储 查询请求 返回原始数据 完整明细结果 💡 核心特征 • 保留所有原始数据 • 无去重、无聚合 • 按 Key 排序存储

2.2 适用场景:什么时候选它?

说实话,我见过不少团队一上来就选聚合模型,结果后面需求变了,想查明细却查不到,只能重建表。所以我的建议是——如果你不确定,先用明细模型。

具体来说,这几类场景特别适合:

  • 日志存储与分析:用户行为日志、访问日志、操作日志。每条记录都是独立的,不需要合并
  • 原始数据保留:金融交易流水、订单记录。每一笔都不能丢,也不能被覆盖
  • 全量数据导出:需要把数据原样导出到其他系统做二次处理
  • 任意维度即席查询:你还没想好固定的分析维度,需要保留所有字段供后续探索
  • 数据审计与回溯:需要查看某个时间点的原始数据状态

我的经验:有一次做用户行为分析平台,业务方一开始说只要 PV/UV 聚合。我留了个心眼,底层用了明细模型。三个月后,他们要做漏斗分析,需要看每个用户的完整行为路径。如果当时用了聚合模型,数据就全废了。所以,明细模型是“后悔药”。

2.3 建表语法:动手写一个

建表语法其实很直观。我直接给你看一个完整的例子:

CREATE TABLE IF NOT EXISTS user_behavior_log (
    event_id        BIGINT           COMMENT '事件ID',
    user_id         VARCHAR(50)      COMMENT '用户ID',
    event_type      VARCHAR(20)      COMMENT '事件类型',
    event_time      DATETIME         COMMENT '事件时间',
    page_url        VARCHAR(500)     COMMENT '页面URL',
    duration_sec    INT              COMMENT '停留时长(秒)',
    extra_info      JSON             COMMENT '扩展信息'
) ENGINE = OLAP
DUPLICATE KEY(event_id, event_time)
DISTRIBUTED BY HASH(user_id) BUCKETS 16
PROPERTIES (
    "replication_num" = "3",
    "storage_medium" = "SSD",
    "storage_cooldown_time" = "2025-12-31 23:59:59"
);

这里有几个要点,我拆开来说:

  • DUPLICATE KEY(event_id, event_time):指定排序列。数据会按 event_id 和 event_time 排序存储。注意,这里不是主键,只是排序用的
  • DISTRIBUTED BY HASH(user_id):分桶键。我习惯用高基数字段做分桶,比如 user_id,这样数据分布更均匀
  • BUCKETS 16:分桶数。一般建议是集群节点数的 3~5 倍
  • replication_num = "3":副本数。生产环境至少 3 副本,保证高可用

⚠️ 注意:DUPLICATE KEY 中指定的列,在建表后不能修改。所以建表前一定要想清楚哪些列是常用的过滤条件。我见过有人把 20 个字段全放进 DUPLICATE KEY,结果前缀索引完全失效,查询性能惨不忍睹。

2.4 数据写入:几种常见方式

明细模型对写入非常友好。因为它不做任何额外处理,写入性能是所有模型中最高的。我常用的写入方式有这几种:

2.4.1 INSERT INTO 单条写入

INSERT INTO user_behavior_log VALUES
(1001, 'user_001', 'page_view', '2025-01-15 10:30:00', '/home', 45, NULL);

2.4.2 批量写入

INSERT INTO user_behavior_log VALUES
(1002, 'user_002', 'click', '2025-01-15 10:31:00', '/product/123', 12, '{"source":"search"}'),
(1003, 'user_003', 'purchase', '2025-01-15 10:32:00', '/checkout', 120, '{"amount":299.00}'),
(1004, 'user_001', 'page_view', '2025-01-15 10:33:00', '/cart', 30, NULL);

2.4.3 Stream Load(推荐方式)

生产环境我基本都用 Stream Load,性能最好:

curl --location-trusted -u root: -H "label:behavior_log_20250115" \
    -H "column_separator:," \
    -T /data/logs/behavior_20250115.csv \
    http://fe_host:8030/api/example_db/user_behavior_log/_stream_load

小技巧:Stream Load 的 label 要保证唯一性,这样即使任务失败重试,也不会产生重复数据。我习惯用「表名_日期_批次号」的命名方式。

2.5 查询示例:看看效果

明细模型的查询,说白了就是直接查原始数据。我写几个典型场景:

2.5.1 查询某个用户的所有行为

SELECT * FROM user_behavior_log
WHERE user_id = 'user_001'
ORDER BY event_time
LIMIT 100;

这个查询会走前缀索引,因为 event_time 是 DUPLICATE KEY 的第二列。如果只查 user_id,那就是全表扫描了。所以建表时,要把最常用的过滤字段放在 DUPLICATE KEY 的前面。

2.5.2 统计某段时间内的事件分布

SELECT event_type, COUNT(*) AS cnt
FROM user_behavior_log
WHERE event_time >= '2025-01-15 00:00:00'
  AND event_time < '2025-01-16 00:00:00'
GROUP BY event_type
ORDER BY cnt DESC;

你看,明细模型也支持 GROUP BY。它是在查询时实时聚合的,不是预聚合。数据量大的时候,性能会受点影响,但胜在灵活。

2.5.3 查看重复数据(明细模型特有的操作)

SELECT event_id, user_id, event_time, COUNT(*) AS dup_cnt
FROM user_behavior_log
GROUP BY event_id, user_id, event_time
HAVING COUNT(*) > 1;

这个查询在聚合模型里是查不到的,因为数据已经被合并了。但在明细模型里,你可以清楚地看到哪些数据被重复写入了。我曾经用这个功能帮业务方排查过数据管道的问题,非常实用。

2.6 性能调优要点

嗯,这里我要说几个实际中容易踩的坑:

优化项 建议 原因
DUPLICATE KEY 列数 不超过 3~5 列 列数太多会导致前缀索引失效,排序开销大
分桶键选择 选高基数字段 数据分布均匀,避免数据倾斜
分桶数 节点数 × 3~5 充分利用并行查询能力
查询过滤 尽量用 DUPLICATE KEY 前列 走前缀索引,避免全表扫描
批量写入大小 每批 10万~100万行 太小则导入频繁,太大则内存压力大

⚠️ 我曾经踩过的坑:有次我把 10 个字段都放进了 DUPLICATE KEY,想着“反正都能排序”。结果查询时发现,只要过滤条件不是前几个字段,性能就极差。后来查文档才知道,StarRocks 的前缀索引只取 DUPLICATE KEY 的前 36 字节。超过的部分,索引就失效了。所以,DUPLICATE KEY 要精不要多。

2.7 小结

明细模型是 StarRocks 最基础的模型,也是我用的最多的模型。它的核心价值就一句话:保留数据的原始面貌。

选它的时候,你不需要纠结“数据要不要聚合”、“要不要去重”。你只需要想清楚:这些数据以后会不会需要看原始记录?如果答案是“会”,那就用明细模型。

我个人习惯是:除非有非常明确的聚合需求,否则一律先用明细模型。等业务稳定了,再考虑要不要换成其他模型。毕竟,从明细模型导出数据做聚合很容易,但从聚合模型还原明细数据,那可就难了。