做Doris开发,几乎都踩过表模型的坑:用聚合表查明细,翻遍结果也找不到单条记录;用主键表做统计,查询跑了很久还没出结果;明细表里订单数据重复堆积却无法去重……
其实问题根源很明确:没吃透Doris的三种核心表模型——明细表(Duplicate Key)、主键表(Unique Key)、聚合表(Aggregate Key)。它们本身没有优劣之分,关键在于“场景适配”。选对模型能让查询性能提升数倍,选错则可能拖累整个集群,甚至无法满足业务需求。
本文将深入解析这三种表模型的底层逻辑、创建方法和适用边界,助你建表时精准决策,一击即中。
一、核心区别:如何处理重复与聚合?
理解表模型的关键在于抓住两个核心:如何处理数据重复以及是否进行预聚合。这两点直接决定了数据的存储方式、查询性能和适用场景。下表清晰地概括了三者的核心差异:
| 表模型 |
核心特征 |
数据存储逻辑 |
查询优势 |
核心局限 |
| 明细表(Duplicate Key) |
允许Key重复,保留全量原始数据 |
按指定Key排序存储,写多少存多少,不聚合不去重 |
支持任意维度即席查询,列存优势明显(只读所需列) |
无预聚合,聚合查询速度慢,存储开销大 |
| 主键表(Unique Key) |
Key唯一,只保留最新版本数据 |
按主键去重,新数据覆盖旧数据,后台清理历史版本 |
支持高频更新、部分列更新,保证数据唯一性 |
不支持ROLLUP预聚合,聚合查询性能一般 |
| 聚合表(Aggregate Key) |
按Key预聚合,只存聚合结果 |
写入时自动按Key分组,指标列执行预聚合(SUM/MAX等) |
聚合查询速度极快,存储开销小 |
不保留原始数据,即席查询能力弱 |
⚠️ 重要提醒:Doris表类型一旦创建便无法修改!建表前务必与业务、产品方确认清楚:是否需要保留原始记录、是否需要唯一性约束、是否需要高频更新。
二、深度解析:原理、建表与场景避坑
1. 明细表:存储原始全量数据
新手常误解“Duplicate Key”,它并非“去重键”,而是“排序键”。数据写入时按此键排序存储,目的是优化查询时的过滤效率,而非去重。例如,指定 (dt, user_id) 为排序键,相同用户的记录会集中存储,查询该用户数据时能快速定位。
(1)建表示例
场景:存储用户行为日志(需保留全量原始记录,支持多维度临时查询)
CREATE TABLE user_behavior_duplicate (
dt DATE COMMENT '日期(排序键列,高频过滤)',
user_id BIGINT COMMENT '用户ID(排序键列,高频过滤)',
action_type VARCHAR(20) COMMENT '行为类型:点击/下单/支付',
goods_id BIGINT COMMENT '商品ID(高频过滤,可建布隆过滤器索引)',
action_time DATETIME COMMENT '行为时间',
device_id VARCHAR(50) COMMENT '设备ID',
channel VARCHAR(30) COMMENT '来源渠道'
)
DISTRIBUTED BY HASH(dt, user_id) BUCKETS 10
DUPLICATE KEY(dt, user_id)
PROPERTIES (
"replication_num" = "3"
);
(2)适用场景
- 原始数据留存:用户行为日志、设备监控日志、金融交易流水存档。
- 多维度即席查询:需灵活组合过滤条件,如“查询某日某地区使用特定设备的用户行为”。
- 发挥列存优势:查询仅涉及部分列时,列式存储能大幅减少I/O开销。
2. 主键表:保证唯一性与支持更新
主键表的核心是“唯一性约束+版本管理”。指定的Unique Key是真正的主键,保证唯一性。数据写入时,相同主键的新记录会覆盖旧版本,后台通过Compaction机制清理历史数据。其最实用的特性是“部分列更新”,例如只更新订单状态,无需传递全量字段,这在处理高频更新的数据库业务表时效率提升显著。
(1)建表示例
场景:电商订单表(订单ID唯一,需高频更新状态)
CREATE TABLE order_unique (
order_id BIGINT COMMENT '订单ID(主键,唯一标识)',
user_id BIGINT COMMENT '用户ID',
goods_id BIGINT COMMENT '商品ID',
order_amt DECIMAL(16, 2) COMMENT '订单金额(静态列,很少更新)',
order_status TINYINT COMMENT '订单状态(高频更新列)',
create_time DATETIME COMMENT '创建时间',
pay_time DATETIME COMMENT '支付时间(高频更新列)',
operator VARCHAR(50) COMMENT '操作人'
)
DISTRIBUTED BY HASH(order_id) BUCKETS 30
UNIQUE KEY(order_id)
PROPERTIES (
"replication_num" = "3"
);
(2)适用场景
- 主键唯一约束:订单、用户、商品等核心业务表,避免数据重复。
- 高频更新:订单状态、物流节点、商品库存的实时更新。
- 部分列更新:仅更新少数字段,无需回填所有列值。
3. 聚合表:预聚合加速查询
聚合表的核心是“写入时预聚合,查询时直接取结果”。建表时指定“聚合Key列”(维度)和带聚合函数的“指标列”,数据写入时即自动按Key分组聚合。例如,100条同一商品的订单记录,最终只存储1条包含总销量的聚合数据。优势是存储量小、聚合查询极快,代价是丢失原始明细。
(1)建表示例
场景:电商运营日报表(按天+商品维度统计,支撑看板秒级查询)
CREATE TABLE goods_sales_aggregate (
dt DATE COMMENT '日期(聚合Key列)',
goods_id BIGINT COMMENT '商品ID(聚合Key列)',
goods_name VARCHAR(100) COMMENT '商品名称(聚合Key列)',
category VARCHAR(50) COMMENT '商品分类(聚合Key列)',
order_cnt SUM BIGINT COMMENT '下单数量(指标列,求和)',
sales_amt SUM DECIMAL(16, 2) COMMENT '销售金额(指标列,求和)',
max_price MAX DECIMAL(10, 2) COMMENT '最高单价(指标列,取最大)'
)
DISTRIBUTED BY HASH(dt, goods_id) BUCKETS 20
AGGREGATE KEY(dt, goods_id, goods_name, category)
PROPERTIES (
"replication_num" = "3"
);
(2)适用场景
- 固定维度报表:运营日报、管理层驾驶舱、销量排行榜,查询维度固定。
- 高频聚合查询:实时GMV统计、实时用户活跃度大盘,要求秒级响应。
- 只需汇总结果:仅关心统计结果(如总销量、平均价),不关心单条明细。
三、场景速查与关键注意事项
场景速查表
| 业务场景 |
推荐模型 |
核心原因 |
| 用户行为日志分析 |
明细表 |
需保留原始日志,支持多维度即席查询 |
| 电商订单管理 |
主键表 |
订单ID唯一,需高频更新状态,兼顾明细查询 |
| 运营日报/看板统计 |
聚合表 |
固定维度预聚合,查询速度快,支持秒级响应 |
| 商品维度表(每日更新) |
主键表 |
商品ID唯一,每日更新价格、库存等信息 |
关键注意事项
- 表类型不可修改:如果选错模型,只能删除表后重建。建议建表前用小批量数据测试验证。
- ROLLUP仅限聚合表:主键表和明细表不支持创建ROLLUP进行进一步预聚合,想利用ROLLUP加速查询只能使用聚合表。
- 更新语义差异:主键表的更新是“覆盖旧值”,而聚合表指标列(如SUM)的更新是“聚合叠加”(新值会与原有结果相加)。
总结
Doris的三种表模型,是为不同数据使用场景量身定制的工具:
- 明细表如同“数据仓库”,负责存储原始明细,保障查询的灵活性。
- 主键表如同“在线业务系统”,保障核心数据的唯一性与实时性。
- 聚合表如同“分析仪表盘”,致力于提供极速的汇总查询体验。
常见的踩坑原因往往不是模型本身不好用,而是“场景与模型错配”。建表前,对照上述决策逻辑与场景速查表仔细斟酌,结合业务需求调整建表参数,便能有效规避性能陷阱,充分发挥Doris的数据处理能力。