订阅更新

同一张表选Duplicate还是Aggregate?选错了性能差10倍

Doris手册 第 2 题 约 10 分钟读完

本文目录
  1. 先说结论
  2. 三种模型详解 + 建表示例
  3. 三种模型对比
  4. 实战经验:我在某元宇宙社交公司的模型选型
  5. 实战踩坑:Aggregate模型丢了明细数据
  6. 面试高频追问
  7. 总结

先说结论

Doris有三种数据模型:Duplicate Key Model(明细模型,保留所有原始数据)、Aggregate Key Model(聚合模型,相同Key自动聚合)、Unique Key Model(唯一模型,相同Key保留最新值)。选型核心原则:明细分析用Duplicate、预聚合场景用Aggregate、维度表/更新场景用Unique。

最关键的区别:Duplicate保留所有行、Aggregate自动合并相同Key的行、Unique只保留相同Key的最新行。选错模型可能导致数据丢失(该保留明细的被聚合了)或性能低下(该预聚合的没聚合)。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

Doris的数据模型是面试的核心考点。很多人只知道有三种模型,但说不清楚什么场景用哪种、为什么这么选、选错了有什么后果。我在某元宇宙社交公司用Doris做实时数仓时,一开始把虚拟礼物交易明细表建成了Aggregate模型,结果明细查不到了——因为被聚合了。后来改成Unique模型才解决。这个踩坑让我深刻理解了三种模型的适用场景。

三种模型详解 + 建表示例

① Duplicate Key Model(明细模型)——最常用

保留所有原始数据行,即使Key完全相同也不合并。适用于需要查看明细的场景:用户行为日志、交易流水、埋点事件。

-- Duplicate模型:用户行为事件明细表
-- 保留每一条事件记录,不做任何聚合
CREATE TABLE ods_user_event (
    event_time DATETIME NOT NULL COMMENT '事件时间',
    event_id VARCHAR(64) NOT NULL COMMENT '事件唯一ID',
    user_id BIGINT COMMENT '用户ID',
    event_name VARCHAR(128) COMMENT '事件名称',
    device_id VARCHAR(64) COMMENT '设备ID',
    properties JSON COMMENT '事件属性',
    app_version VARCHAR(16) COMMENT 'APP版本'
) ENGINE=OLAP
DUPLICATE KEY(event_time, event_id)  -- 排序Key,不去重
PARTITION BY RANGE(event_time) (
    PARTITION p20240115 VALUES LESS THAN ('2024-01-16'),
    PARTITION p20240116 VALUES LESS THAN ('2024-01-17')
)
DISTRIBUTED BY HASH(user_id) BUCKETS 16;

注意:DUPLICATE KEY定义的是排序Key,不是去重Key。数据按这些列排序存储,加速范围查询,但不会删除重复行。

② Aggregate Key Model(聚合模型)——适合预聚合

相同Key的行在导入时自动按定义的聚合方式合并。适用于只需要聚合结果不需要明细的场景:UV/PV统计、GMV汇总。

-- Aggregate模型:每日用户指标聚合表
-- 同一个user_id + dt的数据自动聚合
CREATE TABLE dws_user_daily_stats (
    dt DATE NOT NULL COMMENT '日期',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    -- 以上是Aggregate Key(分组维度)
    pv BIGINT SUM COMMENT '页面浏览量',
    uv BIGINT REPLACE COMMENT '去重后的UV',
    total_amount DECIMAL(12,2) SUM COMMENT '交易总额',
    last_login DATETIME REPLACE COMMENT '最后登录时间',
    max_single_amount DECIMAL(12,2) MAX COMMENT '最大单笔金额'
) ENGINE=OLAP
AGGREGATE KEY(dt, user_id)
PARTITION BY RANGE(dt) (
    PARTITION p20240115 VALUES LESS THAN ('2024-01-16')
)
DISTRIBUTED BY HASH(user_id) BUCKETS 8;
-- 聚合方式:SUM(累加)、REPLACE(替换为最新)、MAX/MIN

核心优势:查询时不需要实时聚合,数据在导入时已经预聚合了,查询速度极快。适合做DWS层的汇总表。

核心陷阱:聚合后明细数据就没了!如果业务后来需要查明细(比如某个用户的每一笔交易),Aggregate模型查不出来。这就是我踩过的坑。

③ Unique Key Model(唯一模型)——适合更新场景

相同Key只保留最新一行。适用于需要更新的维度表、CDC同步表。

-- Unique模型:用户维度表
-- 同一个user_id只保留最新数据(UPSERT语义)
CREATE TABLE dim_user (
    user_id BIGINT NOT NULL COMMENT '用户ID',
    nickname VARCHAR(128) COMMENT '昵称',
    gender TINYINT COMMENT '性别 1男2女',
    country VARCHAR(32) COMMENT '国家',
    register_time DATETIME COMMENT '注册时间',
    vip_level TINYINT COMMENT 'VIP等级',
    last_login DATETIME COMMENT '最后登录时间',
    updated_at DATETIME COMMENT '更新时间'
) ENGINE=OLAP
UNIQUE KEY(user_id)  -- 相同user_id只保留最新行
DISTRIBUTED BY HASH(user_id) BUCKETS 8;
 
-- CDC同步的用户数据写入这张表
-- FlinkCDC的INSERT/UPDATE/DELETE都能正确处理

三种模型对比

维度DuplicateAggregateUnique
数据行为保留所有行相同Key自动聚合相同Key保留最新
写入语义AppendMerge(聚合)Upsert(更新)
查询性能中等(需实时聚合)最快(已预聚合)中等
明细可查✅ 可以❌ 不行✅ 最新值可查
适用层ODS/DWDDWS/ADSDIM/DWD(CDC)
适用场景事件日志、交易明细UV/PV/GMV汇总维度表、用户画像

实战经验:我在某元宇宙社交公司的模型选型

表名模型选型原因
ods_user_eventDuplicate用户行为明细必须保留,运营要查每一条点击记录
ods_gift_txnUnique交易流水用transaction_id去重,同一笔交易更新状态
dws_daily_overviewAggregate每日PV/UV/GMV只需要汇总值,不需要明细
dim_userUnique用户维度表,FlinkCDC同步MySQL,按user_id更新
dim_gift_configUnique礼物配置表,按gift_id更新

实战踩坑:Aggregate模型丢了明细数据

刚上线Doris的时候,我把虚拟礼物交易表建成了Aggregate模型(Key是user_id+dt,amount用SUM聚合)。想法是直接查每个用户每天的交易总额。

上线第二天运营来找我:'能不能帮我查一下user_123昨天的每一笔交易明细?'

我一查——查不到。因为同一个user_id+dt的所有交易已经被SUM合并成一条了,原始的每笔交易金额、交易时间、礼物类型全部丢失。

解决方案:把交易表改成Unique模型(Key是transaction_id),每笔交易一条记录,同一个transaction_id只保留最新状态。需要聚合时在查询SQL里做GROUP BY就行——虽然查询比Aggregate慢一点,但明细数据保住了。

-- 修复前:Aggregate模型(丢了明细)
CREATE TABLE gift_txn_wrong (
    dt DATE, user_id BIGINT,
    amount DECIMAL(12,2) SUM  -- 自动求和,明细丢了!
) AGGREGATE KEY(dt, user_id);
 
-- 修复后:Unique模型(保留每笔交易)
CREATE TABLE gift_txn_right (
    transaction_id VARCHAR(64),
    user_id BIGINT,
    gift_type VARCHAR(32),
    amount DECIMAL(12,2),
    status VARCHAR(16),  -- pending/completed/refunded
    event_time DATETIME
) UNIQUE KEY(transaction_id);  -- 按交易ID去重,状态更新

教训:Aggregate模型的聚合是不可逆的——数据一旦被聚合就找不回明细了。除非你100%确定永远不需要查明细,否则优先选Duplicate或Unique。实际项目中DWS层的汇总表用Aggregate,其他表用Duplicate或Unique。

面试高频追问

追问一:Unique模型底层是怎么实现的?

Doris的Unique模型有两种实现:① Merge-on-Read(默认,读时合并):写入时不去重,查询时实时合并相同Key的数据,写入快但查询有额外开销;② Merge-on-Write(Doris 2.0+推荐):写入时就做去重,查询时不需要合并,查询更快但写入有开销。生产环境如果查询频率远高于写入,建议用Merge-on-Write。

追问二:Duplicate模型的Key有什么用?

Duplicate的Key定义的是排序Key,不是去重Key。数据按Key排序存储,作用是加速范围查询和前缀索引匹配。比如Key是(event_time, event_id),按时间范围查询就能利用有序性快速定位。所以Duplicate的Key要选经常出现在WHERE条件里的列。

追问三:什么时候用Aggregate而不是在查询SQL里GROUP BY?

当你的表数据量非常大(百亿级)、而业务只需要聚合结果时,Aggregate模型的预聚合可以把存储量减少几十倍,查询速度提升数量级。比如一个日活1亿的APP,每天产生100亿条行为日志,建成Aggregate模型按user_id+dt聚合后只有1亿行,查询快得多。但前提是你确定不需要明细。

总结

Doris数据模型面试就记住这个选型口诀:明细保留用Duplicate、维度更新用Unique、汇总预聚合用Aggregate。每种模型讲清楚适用场景和注意事项,特别是Aggregate模型'聚合不可逆'的陷阱。能讲出自己选错模型的踩坑经历和修复方案,面试官会觉得你是真正在生产环境用过Doris的人,而不是照着文档背的。

下一篇聊Doris的分区分桶设计:一张表要不要分区?分多少个桶?这些直接决定了查询性能。

你们的Doris表用的什么数据模型?有没有踩过模型选错的坑?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器