news 2026/5/27 15:10:11

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 列不同

  • 明细表(Duplicate):Key 列为 Aggreate Key,表示排序,没有唯一键的约束
  • 主键表(Unique):Key 列为 Unique Key,表示排序和唯一键约束
  • 聚合表(Aggregate):Key 列为 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. 原理

  1. 不进行任何聚合,所有写入的行都作为独立记录存储。
  2. 表的 Sort Key(前缀列)仅用于排序和构建稀疏索引,不影响数据语义。
  3. 支持完全的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 列必须指定 聚合函数(如SUMMAXMINREPLACEHLL_UNIONBITMAP_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_UNION

HLL 类型的列的聚合方式,通过 HyperLogLog 算法聚合。

BITMAP_UNION

BITMAP 类型的列的聚合方式,进行位图的并集聚合。

3.3. 适用场景

  • 预聚合报表(如每日站点 PV/UV)
  • 指标汇总表(GMV、订单数等)
  • 使用 HLL/Bitmap 做近似去重

3.4. 性能特点

  • 极大减少存储空间(尤其对高重复率数据)
  • 查询性能极高(无需运行时 GROUP BY)
  • 写入时需等待 Compaction 才完成最终聚合(非强实时聚合)

3.5. 限制

在聚合列(Value)上,执行与聚合类型不一致的聚合类查询时,要注意语意。

  • 比如SELECT MIN(cost) FROM table
  • cost列为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 膨胀)
  1. 部分列更新的实现:如果使用聚合模型,而不使用聚合函数,所有 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-Read(MoR)

  • 写入时不合并,新旧版本共存。
  • 查询时实时合并(读时合并):扫描所有版本,取最新(按 sequence col 或导入版本)。
  • 依赖 Delete Bitmap 标记旧版本为“已删除”。
  • 问题:查询性能差(需扫描多版本)、Compaction 压力大、内存消耗高。
  • 场景:写多读少的场景,在查询是需要进行多个版本合并,谓词无法下推,可能会影响到查询速度

4.1.2. 新版(Doris 2.0+):Merge-on-Write(MoW)

🚀 这是 Doris 在 2023 年后最重要的存储引擎升级之一。

  • 写入时直接覆盖旧数据,物理上只保留最新版本。
  • 引入 主键索引(Primary Key Index):
    • 基于 LSM-Tree + RocksDB / 内存 Hash Index(可选)
    • 快速定位待更新行的物理位置(Row Location)
  • 支持 真正的 UPSERT 和 DELETE,语义清晰。
  • 查询时 无需合并,直接读取最新数据,性能接近 Duplicate 模型。
  • 写时合并兼顾查询和写入性能,避免多个版本的数据合并,并支持谓词下推到存储层

4.2. 建表示例

4.2.1. 写时合并

在建表时

  1. 使用UNIQUE KEY关键字:指定主键表
  2. 显式开启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. 适用场景

  • 需要主键唯一约束 的业务表(如用户表、商品表)
  • 频繁更新/删除 的场景(如订单状态变更、用户标签刷新)
  • CDC(Change Data Capture) 数据同步(如 Debezium → Doris)

4.4. 性能特点(MoW vs MoR)

维度

Merge-on-Read (旧)

Merge-on-Write (新)

写入性能

高(追加写)

中(需查主键索引)

查询性能

低(多版本合并)

高(单版本直读)

存储空间

高(存多版本)

低(仅最新版)

更新延迟

最终一致

强一致(事务内可见)

主键索引

有(RocksDB / MemIndex)

4.5. 实践建议(MoW)

  • 主键应尽量短且高基数(避免索引膨胀)
  • 开启light_schema_change支持快速加列
  • 监控主键索引内存使用(可通过SHOW PROC '/frontends'查看)
  • 写入吞吐略低于 Duplicate,但远优于 MoR 的查询性能

4.6. 最新进展(截至 Doris 2.1~2.2)

  1. 支持 Partial Update(部分列更新):只需提供主键 + 待更新列,其他列自动保留。
-- 只更新 age,name 保持不变 INSERT INTO user_profile(user_id, age) VALUES (1001, 28);
  1. 支持 Sequence Column:指定一个列(如update_time)作为版本依据,解决乱序更新问题。
PROPERTIES ( "function_column.sequence_type" = "DATETIME", "function_column.sequence_col" = "update_time" );
  1. 主键索引支持 持久化到磁盘(RocksDB),避免 FE 内存压力过大。

5. 三种模型对比总结

特性

Duplicate Key

Aggregate Key

Unique Key (MoW)

数据语义

保留所有明细

自动聚合

主键唯一,支持更新

是否去重

是(按 Key)

是(按主键)

写入语义

Append-only

Append + 后台聚合

Upsert / Delete

查询性能

中(需扫描明细)

极高(预聚合)

高(单版本)

存储效率

中高

适用场景

日志、事件流

报表、指标汇总

主数据、CDC、画像

是否支持更新

否(需 Delete)

✅ 是

最新能力

-

Bitmap/HLL

Partial Update, Sequence Col

6. 数据模型选型建议

  1. 不确定用哪种?先用 Duplicate Key
    它最灵活,后续可通过 物化视图 派生 Aggregate 或 Unique 视图。
  2. 报表类需求 → Aggregate Key
    如果业务天然就是“按天汇总”,直接建聚合表,省去运行时 GROUP BY。
  3. 需要更新主数据 → Unique Key + MoW
    Doris 2.0+ 强烈推荐开启enable_unique_key_merge_on_write=true
  4. 高并发点查(如用户画像)
    Unique Key + 主键索引 + 合理分桶,可实现 <50ms 的 P99 延迟。
  5. 避免混合模型滥用
    不要为了“既能明细又能聚合”而建多个表——用 物化视图自动同步 更优雅。

7. 数据类型

通过SHOW DATA TYPES;语句查看 Apache Doris 支持的所有数据类型。

7.1. 数值类型

类型名

存储空间(字节)

描述

BOOLEAN

1

布尔值,0 代表false,1 代表true

TINYINT

1

有符号整数,范围 [-128, 127]。

SMALLINT

2

有符号整数,范围 [-32768, 32767]。

INT

4

有符号整数,范围 [-2147483648, 2147483647]

BIGINT

8

有符号整数,范围 [-9223372036854775808, 9223372036854775807]。

LARGEINT

16

有符号整数,范围 [-2^127 + 1 ~ 2^127 - 1]。

FLOAT

4

浮点数,范围 [-3.410^38 ~ 3.410^38]。

DOUBLE

8

浮点数,范围 [-1.7910^308 ~ 1.7910^308]。

DECIMAL

4/8/16/32

高精度定点数,格式:DECIMAL(P[,S])。其中,P 代表一共有多少个有效数字(precision),S 代表小数位有多少数字(scale)。
有效数字 P 的范围是 [1, MAX_P],

注意:Doris 不支持unsigned

7.2. 日期类型

类型名

存储空间(字节)

描述

DATE

4

日期类型,目前的取值范围是 ['0000-01-01', '9999-12-31'],默认的打印形式是'yyyy-MM-dd'

DATETIME

8

日期时间类型,格式:DATETIME([P])。可选参数 P 表示时间精度,取值范围是 [0, 6],即最多支持 6 位小数(微秒)。打印的形式是'yyyy-MM-dd HH:mm:ss.SSSSSS'

其他类型如Timestamp, Time, Year,Doris 不支持

7.3. 字符串类型

类型名

存储空间(字节)

描述

CHAR

M

定长字符串,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 类型

类型名

存储空间(字节)

描述

IPv4

4 字节

以 4 字节二进制存储 IPv4 地址,配合 ipv4_* 系列函数使用。

IPv6

16 字节

以 16 字节二进制存储 IPv6 地址,配合 ipv6_* 系列函数使用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/27 15:09:22

杭州前端AI开发工具组合与使用

杭州前端开发中&#xff0c;AI工具已深度融入实际工作流&#xff0c;主流组合为 Cursor&#xff08;AI原生IDE&#xff09; GitHub Copilot/通义灵码&#xff08;插件&#xff09;。 Cursor 提供Agent模式&#xff0c;支持跨文件重构与终端命令执行&#xff1b; 通义灵码 则凭借…

作者头像 李华
网站建设 2026/5/27 15:07:13

深入解析EAS调度器中的WALT算法:移动设备性能与功耗的平衡艺术

1. 为什么移动设备需要WALT算法&#xff1f; 用手机刷短视频时突然卡顿&#xff0c;玩游戏时莫名发热——这些糟心体验背后&#xff0c;往往藏着CPU调度器的秘密。传统Linux内核的CFS调度器就像个固执的老管家&#xff0c;总想把所有任务平均分给每个CPU核心&#xff0c;却忽略…

作者头像 李华
网站建设 2026/5/27 15:06:33

鸿蒙中 安全随机数的生成

本文同步发表于微信公众号&#xff0c;微信搜索 程语新视界 即可关注&#xff0c;每个工作日都有文章更新 一、安全随机数 安全随机数生成器需要具备三个核心特性&#xff1a; 特性说明随机性生成的数字在统计上均匀分布不可预测性无法根据已知的随机数序列预测后续值不可重现…

作者头像 李华
网站建设 2026/4/7 16:32:37

roboDK--使用

工件去除工件表面油漆按Esc件即可。快捷键/ -- 显示/关闭坐标系名称&#xff1a;坐标系显示/关闭右键坐标系即可。

作者头像 李华
网站建设 2026/4/7 16:31:49

告别多账号内耗,小红书运营原来可以这么轻松

做小红书矩阵运营&#xff0c;每天一到工位&#xff0c;先花十几分钟挨个登录账号&#xff0c;反复扫码验证&#xff0c;刚处理完一个账号的私信&#xff0c;另一个的评论又炸了&#xff1b;来回切换APP、翻找消息&#xff0c;手忙脚乱还容易漏回复。其实想摆脱这种内耗&#x…

作者头像 李华