3. Doris 系列第3篇:数据模型全解析(Duplicate/Aggregate/Unique三大模型+数据类型)

📅 发布时间:2026/7/5 0:31:18 👁️ 浏览次数:
3. Doris 系列第3篇:数据模型全解析(Duplicate/Aggregate/Unique三大模型+数据类型)
开篇核心数据模型是Doris表设计的根基在上一篇中我们掌握了Doris的FE/BE架构分工与分区分桶策略而数据模型则是连接架构与业务的核心桥梁——它决定了数据如何存储、如何聚合、如何更新直接影响查询性能与业务适配性。Apache Doris作为面向实时分析的MPP OLAP数据库针对不同业务场景明细存储、指标聚合、主键更新设计了Duplicate Key、Aggregate Key、Unique Key三大核心数据模型再配合丰富的数据类型支持可满足从日志存储到主数据管理的全场景需求。本文将深入拆解三大模型的原理、适用场景与实践技巧搭配数据类型详解帮你快速完成Doris表设计选型避开常见踩坑点。1. 背景1.1. 为什么需要多种数据模型Apache Doris 是一个面向实时分析的 MPP OLAP 数据库其设计目标是在 高并发、低延迟 场景下高效处理 明细查询 与 聚合查询。不同业务场景对数据语义的要求不同Duplicate Key 模型明细模型有些场景需要保留所有原始明细如日志、行为事件Aggregate Key 聚合模型有些场景天然就是聚合态如 PV/UV 汇总表Unique Key 主键模型有些场景要求主键唯一且支持更新如用户画像、订单状态为此Doris 提供了三种 建表时指定的数据模型Data Model通过 Sort Key排序键 聚合语义 的组合实现存储与计算的深度协同优化。 注意这三种模型均基于 列式存储 排序 索引 架构区别在于 写入时是否自动聚合 以及 如何处理重复主键1.2. 排序键Doris 中数据按列进行排序存储一张表可以分为 Key 列和 Value 列Key 列用于分组与排序可以是一个或多个字段Value 列用于参与聚合建表时不同表类型中Key 列不同明细表DuplicateKey 列为 Aggreate Key表示排序没有唯一键的约束主键表UniqueKey 列为 Unique Key表示排序和唯一键约束聚合表AggregateKey 列为 Aggregate Key表示排序和唯一键约束合理使用排序键的优势加速查询性能有助于减少数据的扫描量数据压缩优化有序存储提高压缩效率减少去重成本针对 Unique 表1.3. Doris 中的表概述创建表CREATE TABLECREATE TABLE LIKECREATE TABLE AS表名默认大小写敏感默认表名最大长度为 64 字节均可配置表属性建表时可通过PROPERTIES指定作用于分区包括分桶数buckets决定数据在表中的分布存储介质storage_medium控制数据的存储方式如使用 HDD、SSD 或远程共享存储副本数 (replication_num)控制数据副本的数量以保证数据的冗余和可靠性冷热分离存储策略 (storage_policy) 控制数据的冷热分离存储的迁移策略修改表属性只对未来创建的分区生效对已经创建好的分区不生效2. Duplicate Key 模型明细模型2.1. 原理不进行任何聚合所有写入的行都作为独立记录存储。表的 Sort Key前缀列仅用于排序和构建稀疏索引不影响数据语义。支持完全的INSERT/DELETE/UPDATE通过 Unique 模型替代或借助 Routine Load Delete但原生 Duplicate 模型本身 不保证主键唯一性。2.2. 创建与使用通过DUPLICATE KEY指定数据存储的排序列建议选择三列或更少的列作为排序键建表说明在建表时可以通过DUPLICATE KEY关键字指定明细表。明细表必须指定数据的 Key 列用于在存储时对数据进行排序。CREATE TABLE IF NOT EXISTS example_tbl_duplicate ( log_time DATETIME NOT NULL, log_type INT NOT NULL, ... ) DUPLICATE KEY(log_time, log_type, error_code) DISTRIBUTED BY HASH(log_type) BUCKETS 10;数据插入与存储在明细表中数据不进行去重与聚合插入数据即存储数据采用追加方式Append存储INSERT INTO即追加数据2.3. 适用场景原始日志存储如 Nginx 日志、APP 埋点需要保留全量明细 的分析场景如漏斗分析、路径分析后续通过物化视图做聚合2.4. 性能特点写入吞吐高无聚合开销查询可利用 Sort Key 做谓词下推和 Zone Map 跳过存储空间较大无去重/聚合压缩2.5. 实践建议Sort Key 应选择 高频过滤字段 高基数字段如时间 user_id避免将低基数字段放前面导致索引效果差3. Aggregate Key 模型聚合模型3.1. 原理在写入时自动按 Sort Key 聚合相同 Sort Key 的行会被合并。非 Sort Key 的 Value 列必须指定 聚合函数如SUM、MAX、MIN、REPLACE、HLL_UNION、BITMAP_UNION不支持UPDATE / DELETE因为语义是“不断累加”聚合发生在 BE 节点本地 Compaction 阶段非实时但最终一致数据导入阶段数据按批次导入每批次生成一个版本并对相同聚合键的数据进行初步聚合后台文件合并阶段Compaction多个版本文件会定期合并减少冗余并优化存储查询阶段查询时系统会聚合同一聚合键的数据确保查询结果准确。3.2. 创建与使用使用AGGREGATE KEY关键字在建表时指定聚合表并指定 Key 列用于聚合 Value 列。CREATE TABLE IF NOT EXISTS example_tbl_agg ( user_id LARGEINT NOT NULL, load_dt DATE NOT NULL, city VARCHAR(20), last_visit_dt DATETIME REPLACE DEFAULT 1970-01-01 00:00:00, cost BIGINT SUM DEFAULT 0, max_dwell INT MAX DEFAULT 0, ) AGGREGATE KEY(user_id, load_dt, city) DISTRIBUTED BY HASH(user_id) BUCKETS 10;数据导入时Key 列会聚合成一行Value 列会按照指定的聚合类型进行维度聚合。Value 列支持以下类型的维度聚合聚合方式描述SUM求和多行的 Value 进行累加。REPLACE替代下一批数据中的 Value 会替换之前导入过的行中的 Value。MAX保留最大值。MIN保留最小值。REPLACE_IF_NOT_NULL非空值替换。与 REPLACE 的区别在于对null值不做替换。HLL_UNIONHLL 类型的列的聚合方式通过 HyperLogLog 算法聚合。BITMAP_UNIONBITMAP 类型的列的聚合方式进行位图的并集聚合。3.3. 适用场景预聚合报表如每日站点 PV/UV指标汇总表GMV、订单数等使用 HLL/Bitmap 做近似去重3.4. 性能特点极大减少存储空间尤其对高重复率数据查询性能极高无需运行时 GROUP BY写入时需等待 Compaction 才完成最终聚合非强实时聚合3.5. 限制在聚合列Value上执行与聚合类型不一致的聚合类查询时要注意语意。比如SELECT MIN(cost) FROM tablecost列为SUM列此时MIN的结果是MIN(SUM(cost))而非直接导入的cost值一致性保证在某些查询中会极大地降低查询效率。针对基本的COUNT(*)Doris 必须扫描所有的 AGGREGATE KEY 列并且聚合后才能得到语意正确的结果当聚合列非常多时COUNT(*)查询需要扫描大量的数据当业务上有频繁的COUNT(*)查询时获取原始数据的导入行数增加一个值恒为 1 的聚合类型为SUM的列COUNT(*)等价于SUM(COUNT)后者的查询效率将远高于前者。获取聚合后的行数增加一个值恒为 1 的聚合类型为REPLACE的列COUNT(*)等价于SUM(COUNT)后者的查询效率将远高于前者。3.6. 实践建议聚合列必须显式声明函数不能混用普通列避免在高基数维度上做聚合会导致 Tablet 膨胀部分列更新的实现如果使用聚合模型而不使用聚合函数所有 Value 列均为REPLACE_IF_NOT_NULL那么Aggregate Key REPLACE_IF_NOT_NULL UNIQUE KEY就可以使用 Doris 实现部分列更新4. Unique Key 模型主键模型这是 Doris 最复杂也最具演进性 的模型经历了 Merge-on-Read → Merge-on-Write 的重大架构升级。4.1. 原理演进4.1.1. 旧版Merge-on-ReadMoR写入时不合并新旧版本共存。查询时实时合并读时合并扫描所有版本取最新按 sequence col 或导入版本。依赖 Delete Bitmap 标记旧版本为“已删除”。问题查询性能差需扫描多版本、Compaction 压力大、内存消耗高。场景写多读少的场景在查询是需要进行多个版本合并谓词无法下推可能会影响到查询速度4.1.2. 新版Doris 2.0Merge-on-WriteMoW 这是 Doris 在 2023 年后最重要的存储引擎升级之一。写入时直接覆盖旧数据物理上只保留最新版本。引入 主键索引Primary Key Index基于 LSM-Tree RocksDB / 内存 Hash Index可选快速定位待更新行的物理位置Row Location支持 真正的 UPSERT 和 DELETE语义清晰。查询时 无需合并直接读取最新数据性能接近 Duplicate 模型。写时合并兼顾查询和写入性能避免多个版本的数据合并并支持谓词下推到存储层4.2. 建表示例4.2.1. 写时合并在建表时使用UNIQUE KEY关键字指定主键表显式开启enable_unique_key_merge_on_write属性指定写时合并模式自 Doris 2.1 版本以后默认开启写时合并CREATE TABLE IF NOT EXISTS example_tbl_unique ( user_id LARGEINT NOT NULL, user_name VARCHAR(50) NOT NULL ) UNIQUE KEY(user_id, user_name) DISTRIBUTED BY HASH(user_id) BUCKETS 10 PROPERTIES ( enable_unique_key_merge_on_write true );4.2.2. 读时合并在建表时使用UNIQUE KEY关键字指定主键表显示关闭enable_unique_key_merge_on_write属性指定读时合并模式在 Doris 2.1 版本之前默认开启读时合并CREATE TABLE IF NOT EXISTS example_tbl_unique ( user_id LARGEINT NOT NULL, username VARCHAR(50) NOT NULL ) UNIQUE KEY(user_id, username) DISTRIBUTED BY HASH(user_id) BUCKETS 10 PROPERTIES ( enable_unique_key_merge_on_write false );4.3. 适用场景需要主键唯一约束 的业务表如用户表、商品表频繁更新/删除 的场景如订单状态变更、用户标签刷新CDCChange Data Capture 数据同步如 Debezium → Doris4.4. 性能特点MoW vs MoR维度Merge-on-Read (旧)Merge-on-Write (新)写入性能高追加写中需查主键索引查询性能低多版本合并高单版本直读存储空间高存多版本低仅最新版更新延迟最终一致强一致事务内可见主键索引无有RocksDB / MemIndex4.5. 实践建议MoW主键应尽量短且高基数避免索引膨胀开启light_schema_change支持快速加列监控主键索引内存使用可通过SHOW PROC /frontends查看写入吞吐略低于 Duplicate但远优于 MoR 的查询性能4.6. 最新进展截至 Doris 2.1~2.2支持 Partial Update部分列更新只需提供主键 待更新列其他列自动保留。-- 只更新 agename 保持不变 INSERT INTO user_profile(user_id, age) VALUES (1001, 28);支持 Sequence Column指定一个列如update_time作为版本依据解决乱序更新问题。PROPERTIES ( function_column.sequence_type DATETIME, function_column.sequence_col update_time );主键索引支持 持久化到磁盘RocksDB避免 FE 内存压力过大。5. 三种模型对比总结特性Duplicate KeyAggregate KeyUnique Key (MoW)数据语义保留所有明细自动聚合主键唯一支持更新是否去重否是按 Key是按主键写入语义Append-onlyAppend 后台聚合Upsert / Delete查询性能中需扫描明细极高预聚合高单版本存储效率低高中高适用场景日志、事件流报表、指标汇总主数据、CDC、画像是否支持更新否需 Delete否✅ 是最新能力-Bitmap/HLLPartial Update, Sequence Col6. 数据模型选型建议不确定用哪种先用 Duplicate Key它最灵活后续可通过 物化视图 派生 Aggregate 或 Unique 视图。报表类需求 → Aggregate Key如果业务天然就是“按天汇总”直接建聚合表省去运行时 GROUP BY。需要更新主数据 → Unique Key MoWDoris 2.0 强烈推荐开启enable_unique_key_merge_on_writetrue。高并发点查如用户画像Unique Key 主键索引 合理分桶可实现 50ms 的 P99 延迟。避免混合模型滥用不要为了“既能明细又能聚合”而建多个表——用 物化视图自动同步 更优雅。7. 数据类型通过SHOW DATA TYPES;语句查看 Apache Doris 支持的所有数据类型。7.1. 数值类型类型名存储空间字节描述BOOLEAN1布尔值0 代表false1 代表true。TINYINT1有符号整数范围 [-128, 127]。SMALLINT2有符号整数范围 [-32768, 32767]。INT4有符号整数范围 [-2147483648, 2147483647]BIGINT8有符号整数范围 [-9223372036854775808, 9223372036854775807]。LARGEINT16有符号整数范围 [-2^127 1 ~ 2^127 - 1]。FLOAT4浮点数范围 [-3.410^38 ~ 3.410^38]。DOUBLE8浮点数范围 [-1.7910^308 ~ 1.7910^308]。DECIMAL4/8/16/32高精度定点数格式DECIMAL(P[,S])。其中P 代表一共有多少个有效数字precisionS 代表小数位有多少数字scale。有效数字 P 的范围是 [1, MAX_P]注意Doris 不支持unsigned7.2. 日期类型类型名存储空间字节描述DATE4日期类型目前的取值范围是 [0000-01-01, 9999-12-31]默认的打印形式是yyyy-MM-ddDATETIME8日期时间类型格式DATETIME([P])。可选参数 P 表示时间精度取值范围是 [0, 6]即最多支持 6 位小数微秒。打印的形式是yyyy-MM-dd HH:mm:ss.SSSSSS其他类型如Timestamp, Time, YearDoris 不支持7.3. 字符串类型类型名存储空间字节描述CHARM定长字符串M 代表的是定长字符串的字节长度。M 的范围是 1-255。VARCHAR不定长变长字符串M 代表的是变长字符串的字节长度。M 的范围是 1-65533。变长字符串是以 UTF-8 编码存储的因此通常英文字符占 1 个字节中文字符占 3 个字节。STRING不定长变长字符串默认支持 1048576 字节1MB可调大到 2147483643 字节2GB。String 类型只能用在 Value 列不能用在 Key 列和分区分桶列7.4. 半结构类型类型名存储空间字节描述ARRAY不定长由 T 类型元素组成的数组不能作为 Key 列使用。目前支持在 Duplicate 和 Unique 模型的表中使用。MAP不定长由 K, V 类型元素组成的 map不能作为 Key 列使用。目前支持在 Duplicate 和 Unique 模型的表中使用。STRUCT不定长由多个 Field 组成的结构体也可被理解为多个列的集合。不能作为 Key 使用目前 STRUCT 仅支持在 Duplicate 模型的表中使用。一个 Struct 中的 Field 的名字和数量固定总是为 Nullable。JSON不定长二进制 JSON 类型采用二进制 JSON 格式存储通过 JSON 函数访问 JSON 内部字段。长度限制和配置方式与 String 相同VARIANT不定长动态可变数据类型专为半结构化数据如 JSON 设计可以存入任意 JSON自动将 JSON 中的字段拆分成子列存储提升存储效率和查询分析性能。长度限制和配置方式与 String 相同。Variant 类型只能用在 Value 列不能用在 Key 列和分区分桶列。7.5. IP 类型类型名存储空间字节描述IPv44 字节以 4 字节二进制存储 IPv4 地址配合 ipv4_* 系列函数使用。IPv616 字节以 16 字节二进制存储 IPv6 地址配合 ipv6_* 系列函数使用。