Apache Doris 建表全指南:模型选型、表属性配置、分区与分桶深度解析
一、引言:为什么建表是 Doris 优化的第一步?
在 Apache Doris 中,建表绝非简单的 CREATE TABLE 语句。数据模型、分区策略、分桶方式的选择,直接决定数据写入性能、查询速度、存储成本和维护复杂度。三者选对了,后续运维与查询事半功倍;选错了,数据迁移和重构将带来巨大的代价。
本文将分为五个完整章节,逐一剖析:
-
数据模型对比:三种模型的原理、适用场景与避坑指南
-
表属性(PROPERTIES)详解:副本数、存储介质、压缩方式等关键配置
-
分区策略全解:手动分区、动态分区、自动分区的用法与场景
-
分桶策略全解:分桶键选择、分桶数量计算、Hash/Random 分桶对比
-
综合实战示例:一个完整的数据仓库建表示例
每个章节都附带完整 SQL 示例和决策建议,便于读者直接参考使用。
二、数据模型:掌握“一失足成千古恨”的第一步
Doris 提供三种数据模型:Duplicate Key(明细模型)、Unique Key(主键模型)、Aggregate Key(聚合模型)。建表时确定后无法修改,所以必须根据业务场景谨慎选择。
2.1 三种模型的核心差异速览
| 对比维度 | Duplicate Key | Unique Key | Aggregate Key |
|---|---|---|---|
| 数据保留 | 保留全量原始记录,无任何去重 | 同 Key 仅保留最新版本 | 同 Key 按聚合函数合并,无明细 |
| 写入性能 | 最高(无冲突检查) | 中等(需主键冲突处理) | 最低(需实时聚合计算) |
| 查询性能 | Ad-hoc 灵活,但聚合查询较慢 | 主键点查快,聚合查询一般 | 固定维度聚合极快 |
| 存储占用 | 最大 | 中等 | 最小(预聚合压缩) |
| 典型场景 | 日志存储、用户行为明细 | 订单状态、用户画像、库存 | PV/UV 统计、销售额汇总 |
2.2 Duplicate Key——明细模型
原理:允许 Key 列重复,Doris 保留所有写入的原始数据,不做去重或聚合。Key 列在这里仅起排序作用,不保证唯一。
适用场景:
-
原始日志存储(Nginx 日志、APP 埋点、传感器数据)
-
需全量明细的分析(用户行为漏斗、故障排查)
-
后续通过物化视图或查询层做灵活聚合的场景
建表示例:
CREATE TABLE user_behavior (
user_id BIGINT NOT NULL COMMENT "用户ID",
action_time DATETIME NOT NULL COMMENT "行为时间",
action_type VARCHAR(50) NOT NULL COMMENT "行为类型",
page_url VARCHAR(500) COMMENT "页面URL",
duration INT DEFAULT 0 COMMENT "停留时长(秒)"
) DUPLICATE KEY(user_id, action_time)
PARTITION BY RANGE(action_time) ()
DISTRIBUTED BY HASH(user_id) BUCKETS 32
PROPERTIES("replication_num" = "3");
⚠️ 核心注意:DUPLICATE KEY 不是去重键,而是排序键,建议选择高频过滤字段作为 Key,利用 Doris 的列存和 Zone Map 加速查询。
2.3 Unique Key——主键模型
原理:确保每条记录的主键唯一,新数据自动覆盖同 Key 的旧数据。Unique Key 本质上是 Aggregate Key 在 Value 列使用 REPLACE 聚合函数的特例。
适用场景:
-
订单系统(订单状态变更、金额实时调整)
-
用户画像(个人信息更新、会员等级变更)
-
账户余额、库存数量等需要精确最新值的场景
建表示例:
CREATE TABLE order_info (
order_id BIGINT NOT NULL COMMENT "订单ID",
user_id BIGINT NOT NULL COMMENT "用户ID",
order_status STRING NOT NULL COMMENT "订单状态",
total_amount DECIMAL(20,2) DEFAULT 0 COMMENT "订单总金额",
update_time DATETIME NOT NULL COMMENT "更新时间"
) UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 16
PROPERTIES("replication_num" = "3");
⚠️ 核心注意:
-
Unique Key 模型写入时需要处理主键冲突,写入性能低于 Duplicate 模型
-
从 Doris 2.0 版本开始支持部分列更新,需要先开启会话变量
SET enable_unique_key_partial_update = true
2.4 Aggregate Key——聚合模型
原理:导入时按 Key 列分组,对 Value 列执行预定义的聚合函数(SUM/MAX/MIN/REPLACE 等),最终只存储聚合后的结果。
适用场景:
-
实时报表(按天/小时的 PV、UV 统计)
-
广告点击量和销售额汇总
-
物联网设备指标聚合(平均温度、最大压力)
建表示例:
CREATE TABLE daily_sales_stats (
sale_date DATE NOT NULL COMMENT "销售日期",
product_id BIGINT NOT NULL COMMENT "商品ID",
total_amount DECIMAL(20,2) SUM DEFAULT "0" COMMENT "总销售额(SUM)",
sale_count BIGINT SUM DEFAULT "0" COMMENT "销售次数(SUM)",
max_price DECIMAL(10,2) MAX DEFAULT "0" COMMENT "最高单价(MAX)"
) AGGREGATE KEY(sale_date, product_id)
PARTITION BY RANGE(sale_date) ()
DISTRIBUTED BY HASH(product_id) BUCKETS 24
PROPERTIES("replication_num" = "3");
⚠️ 核心注意:
-
Aggregate Key 模型对
COUNT(*)查询不友好,因为原始明细已被聚合 -
Value 列的聚合函数类型一旦确定不可更改,后续查询其他聚合形式时需注意语义一致
-
如需部分列更新,可使用
REPLACE_IF_NOT_NULL聚合函数
2.5 模型选择的决策树
是否需要保留全量原始明细?
├─ YES → 选 Duplicate Key
│
└─ NO → 需要主键去重和更新能力吗?
├─ YES → 选 Unique Key
│
└─ NO → 具有固定维度的聚合统计需求?
├─ YES → 选 Aggregate Key
└─ NO → 重新评估业务,回到 Duplicate Key
三、表属性(PROPERTIES):不可忽视的“隐形调优项”
建表语句中 PROPERTIES 子句可以精细控制数据的存储和分桶策略。这些属性作用于分区级别,修改表属性只对未来创建的分区生效,对已有分区不生效。
3.1 副本数(replication_num)
控制每个 Tablet 的数据副本数量,用于保证数据冗余和可靠性。
PROPERTIES("replication_num" = "3")
使用指南:
-
默认为 3,生产环境必须 >= 2,1 副本在节点故障时会导致数据丢失
-
测试或非关键数据可适当降为 2
-
多副本还可在查询时利用多节点并行读取,提升 Query IO 并发度
3.2 存储介质(storage_medium)
控制数据的存储方式,支持 HDD、SSD 或远程共享存储(如 S3/HDFS)。
PROPERTIES("storage_medium" = "SSD")
使用指南:
-
热数据频繁访问,建议使用 SSD 提升查询性能
-
冷数据或归档数据,可使用 HDD 或冷热分层,降低存储成本
-
使用 SSD 的前提是 BE 节点已正确配置存储路径类型(需要在 be.conf 中标识 SSD 目录)
3.3 冷热分离策略(storage_policy)
Doris 支持将冷数据自动迁移到 HDD 或 S3/HDFS 等低成本存储。
CREATE RESOURCE IF NOT EXISTS "my_s3"
PROPERTIES(
"type" = "s3",
"AWS_ENDPOINT" = "s3.us-east-1.amazonaws.com",
"AWS_REGION" = "us-east-1",
"AWS_ACCESS_KEY" = "your_access_key",
"AWS_SECRET_KEY" = "your_secret_key"
);
CREATE STORAGE POLICY my_cold_policy
PROPERTIES(
"storage_resource" = "my_s3",
"cooldown_datetime" = "2025-12-31 23:59:59"
);
CREATE TABLE my_table (
id INT, data STRING
) DISTRIBUTED BY HASH(id) BUCKETS 10
PROPERTIES(
"storage_policy" = "my_cold_policy"
);
使用指南:
-
适用于日志归档、历史数据长期存储
-
先在集群中创建资源(如 S3/HDFS)和存储策略,再在建表属性中引用
3.4 其他常用表属性
| 属性名 | 说明 | 使用场景 |
|---|---|---|
bloom_filter_columns |
为某些列创建 BloomFilter 索引 | 对非 Key 列进行高并发点查时,加速 IN 和 = 查询 |
compression |
数据压缩方式(LZ4/ZSTD) |
ZSTD 压缩比高但 CPU 开销也大,需根据性能要求平衡 |
light_schema_change |
是否启用轻量级 Schema Change | 默认为 false;开启后加减 Value 列可秒级完成 |
3.5 表属性的实际应用建议
| 场景 | 推荐的 PROPERTIES 配置 |
|---|---|
| 生产核心报表 | "replication_num" = "3", "storage_medium" = "SSD" |
| 测试/临时表 | "replication_num" = "1"(快速测试,生产禁用) |
| 历史归档数据 | "storage_policy" = "cold_policy", "compression" = "ZSTD" |
| 高并发点查 | "bloom_filter_columns" = "order_id,user_id" |
四、分区(Partition):海量数据管理的基石
分区是基于分区列将一个大表拆分成多个物理上的独立子表(Partition)。合理分区的优势非常明显:查询时通过 分区裁剪 快速跳过无关数据、方便按时间维度管理数据生命周期、便于并行处理。
Doris 支持三种分区方式:手动分区、动态分区、自动分区(Auto Partition)。三者各有侧重,需要根据业务场景精准选择。
4.1 手动分区
原理:在建表时显式指定分区列表,或使用 ALTER TABLE ADD PARTITION 动态添加。
适用场景:
-
分区规则固定、范围已知(如按年/月划分历史数据)
-
数据来源和写入时间可控,不需要系统自动创建
使用示例:
-- Range 分区:按日期范围划分
CREATE TABLE sales_range (
sale_date DATE NOT NULL,
product_id BIGINT NOT NULL,
amount DECIMAL(10,2)
) DUPLICATE KEY(sale_date, product_id)
PARTITION BY RANGE(sale_date) (
PARTITION p202401 VALUES LESS THAN ("2024-02-01"),
PARTITION p202402 VALUES LESS THAN ("2024-03-01"),
PARTITION p202403 VALUES LESS THAN ("2024-04-01")
)
DISTRIBUTED BY HASH(product_id) BUCKETS 16;
-- List 分区:按枚举值划分(如按省份)
CREATE TABLE user_region (
user_id BIGINT NOT NULL,
region VARCHAR(20) NOT NULL,
register_time DATETIME
) DUPLICATE KEY(user_id)
PARTITION BY LIST(region) (
PARTITION p_north VALUES IN ("北京","天津","河北"),
PARTITION p_south VALUES IN ("广东","广西","海南")
)
DISTRIBUTED BY HASH(user_id) BUCKETS 16;
⚠️ 手动分区的局限性:当分区规则频繁变化(如新增分区批次),就需要运维人员介入 DDL,维护成本较高。
4.2 动态分区(Dynamic Partition)
原理:系统按预设规则(DAY/WEEK/MONTH)自动增加新分区并回收过期分区。通过 dynamic_partition.start 和 end 参数控制保留的过往分区数和未来预创建分区数,实现分区的 TTL 生命周期管理。
适用场景:
-
日志管理、时序数据等按天/小时滚动的场景
-
需要自动删除陈旧数据的场景
使用示例:
CREATE TABLE log_data (
log_time DATETIME NOT NULL,
log_level VARCHAR(20),
message TEXT
) DUPLICATE KEY(log_time)
PARTITION BY RANGE(log_time) ()
DISTRIBUTED BY HASH(log_time) BUCKETS 10
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-7", -- 保留过去7天
"dynamic_partition.end" = "3", -- 预创建未来3天
"dynamic_partition.prefix" = "p",
"dynamic_partition.create_history_partition" = "true"
);
⚠️ 动态分区的注意事项:
-
必须配合
PARTITION BY RANGE且分区列必须是DATE或DATETIME类型 -
动态分区与跨集群复制功能不兼容
-
小表(2千万条以内)不建议使用动态分区,否则会产生大量无用分区分桶,增加元数据压力
4.3 自动分区(Auto Partition——Doris 2.1+ 新特性)
Auto Partition 是 Apache Doris 2.1.0 推出的新一代自动化分区功能,同时支持按时间维度的 Range 分区和多种数据类型的 List 分区。它会在数据导入过程中自动检测分区是否存在,若不存在则自动创建,极大地简化了分区表维护工作。
技术优势对比:
| 维度 | Auto Partition | Dynamic Partition |
|---|---|---|
| 分区列限制 | 支持多种数据类型 | 仅限 DATE/DATETIME |
| 分区规则 | 导入时自动按需创建 | 按固定时间单位(DAY/WEEK/MONTH)预先创建 |
| 历史分区 | 自动创建 | 需指定 start 参数 |
| 分区名称 | 与原始值或日期格式一致 | 指定固定前缀 |
| 使用难度 | 极简,无需预先声明分区方案 | 需配置多个参数 |
使用示例:
-- Auto Range Partition:按日期自动分区
CREATE TABLE auto_range_sales (
sale_date DATE NOT NULL,
product_id BIGINT NOT NULL,
amount DECIMAL(10,2)
) DUPLICATE KEY(sale_date, product_id)
PARTITION BY RANGE(sale_date) AUTO
DISTRIBUTED BY HASH(product_id) BUCKETS 16;
-- 导入数据后,系统将自动创建分区
INSERT INTO auto_range_sales VALUES
("2024-03-15", 101, 100.00),
("2024-03-16", 102, 200.00);
-- Auto List Partition:按枚举值自动分区
CREATE TABLE auto_list_orders (
order_region VARCHAR(20) NOT NULL,
order_id BIGINT NOT NULL,
amount DECIMAL(10,2)
) DUPLICATE KEY(order_region, order_id)
PARTITION BY LIST(order_region) AUTO
DISTRIBUTED BY HASH(order_id) BUCKETS 16;
推荐:在新版本环境中,专家推荐优先考虑使用 Auto Partition 替代动态分区。
4.4 分区策略选型建议
| 使用场景 | 推荐分区策略 | 理由 |
|---|---|---|
| 日志/时序数据,按天滚动写入 | Auto Partition(2.1+)或动态分区 | 自动化生命周期管理,运维成本低 |
| 历史分区已确定,无需自动创建 | 手动分区 | 规则固定,简单明确 |
| 分区维度灵活多变(如非日期字段) | Auto Partition List 分区 | 自动适应新值出现,无需 DDL 介入 |
| 需要跨区间的复杂分区逻辑 | 手动分区 | 灵活定义不同分区范围 |
分区管理的辅助命令:
-- 查看所有分区
SHOW PARTITIONS FROM table_name;
-- 手动添加分区
ALTER TABLE table_name ADD PARTITION p202405 VALUES LESS THAN ("2024-06-01");
-- 删除旧分区
ALTER TABLE table_name DROP PARTITION p202301;
-- 查看动态分区调度情况
SHOW DYNAMIC PARTITION TABLES;
五、分桶(Bucketing):并行与数据均衡的关键
分区之下,数据会进一步划分为分桶(Bucket),每个分桶对应一个物理存储单元 Tablet。Tablet 是 Doris 多副本高可用和数据调度与均衡的最小物理存储单位。一个表的 Tablet 总数 = 分区数 × 分桶数。
5.1 分桶方式:Hash vs Random
Hash 分桶(默认推荐)
基于分桶列的哈希值将数据均匀分布到各个桶中,适合按指定列进行精准过滤和 JOIN 的场景。
DISTRIBUTED BY HASH(user_id) BUCKETS 16
Random 分桶
数据随机分散到各桶,无需选择分桶键,天然避免数据倾斜。
DISTRIBUTED BY RANDOM BUCKETS 16
⚠️ Random 分桶的限制:只有 Duplicate Key 表支持 Random 分桶,Unique 和 Aggregate 表无法使用。同时,Random 分桶无法利用分桶键进行裁剪,点查时性能不如 Hash 分桶。
5.2 分桶键(Bucket Key)的选择原则
分桶键的选择直接影响数据分布和查询性能,应遵循以下原则:
-
高基数:选择值分布均匀的列(如 user_id, order_id),避免 Gender、Status 等低基数列引起数据倾斜
-
高频查询列:分桶键常作为查询的 WHERE 过滤条件,可利用 桶裁剪(Bucket Pruning) 快速定位
-
多分桶键的权衡:
-
多列分桶 → 数据更均匀,但查询需匹配所有桶列才能裁剪,适合高吞吐低并发场景
-
单列分桶 → 点查可仅扫描一个桶,适合高并发点查场景
-
-
模型限制:Unique 和 Aggregate 模型的分桶键必须是 Key 列;Duplicate 模型则可包含 Value 列
5.3 分桶数量的计算与选择
分桶数的确定需要综合考量数据量、集群规模和未来扩容需求。以下是核心计算原则:
Tablet 总数 = 分区数 × 分桶数 每个 Tablet 推荐大小:1GB ~ 10GB 每个 Tablet 最小建议:≥ 100MB
实战计算指南:
| 总数据量 | 目标 Tablet 大小 | 建议分桶数 | 说明 |
|---|---|---|---|
| 500MB | 100~250MB | 4~8 | 小表,避免 Tablet 数量过少 |
| 5GB | 300~600MB | 8~16 | 中等表,平衡并行度 |
| 50GB | 1~2GB | 32~64 | 推荐按照分区划分 |
| 500GB | ~50GB/分区,16~32/分区 | 建议先按天分区 | 大表,分区+分桶组合 |
| 5TB | ~50GB/分区,16~32/分区 | 建议按小时/天分区 | 超大规模,精细规划 |
推荐做法:
-
新手/不确定数据量:使用
BUCKETS AUTO,Doris 自动根据数据量动态推算分桶数 -
进阶/稳定数据量:手动计算后指定分桶数,并预留扩容空间
-
已经分区:可以在添加新分区时单独指定分桶数,灵活应对数据膨胀或缩小
⚠️ 重要注意:
-
分区的分桶数一旦指定,不可修改
-
若预计未来会扩容集群,初始分桶数不宜过少(如 3 个 BE 设置 3 个分桶),否则扩容后无法提升并行度
-
分桶过多会导致 Tablet 数量过大,增加 FE 元数据压力
5.4 分桶配置完整示例
-- 使用自动分桶(Doris 自动推算分桶数)
CREATE TABLE auto_bucket_log (
log_time DATETIME,
user_id BIGINT,
action STRING
) DUPLICATE KEY(log_time, user_id)
PARTITION BY RANGE(log_time) AUTO
DISTRIBUTED BY HASH(user_id) BUCKETS AUTO;
-- 手动指定分桶数 + 动态分区
CREATE TABLE product_sales (
sale_date DATE,
product_id BIGINT,
sale_amount DECIMAL(20,2)
) DUPLICATE KEY(sale_date, product_id)
PARTITION BY RANGE(sale_date) ()
DISTRIBUTED BY HASH(product_id) BUCKETS 24
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY"
);
六、归纳总结:从理论到实战的综合建表示例
下面是一个近乎工业级的电商数仓 ODS(操作数据存储)层建表,涵盖本文讲解的所有知识点。
6.1 完整建表 SQL
CREATE DATABASE IF NOT EXISTS dwd_ecommerce;
USE dwd_ecommerce;
-- 用户行为明细表:保留全量原始数据,按天分区,按 user_id 分桶
CREATE TABLE dwd_user_behavior (
user_id BIGINT NOT NULL COMMENT "用户ID",
action_time DATETIME NOT NULL COMMENT "行为时间",
action_type VARCHAR(50) NOT NULL COMMENT "行为类型",
product_id BIGINT COMMENT "商品ID",
event_params STRING COMMENT "事件扩展参数(JSON)"
) DUPLICATE KEY(user_id, action_time)
PARTITION BY RANGE(action_time) AUTO -- 自动分区,按需创建
DISTRIBUTED BY HASH(user_id) BUCKETS AUTO -- 自动分桶,无需手动计算
PROPERTIES (
"replication_num" = "3", -- 生产环境 3 副本
"storage_medium" = "SSD", -- 热数据存 SSD
"compression" = "LZ4", -- 平衡压缩比和 CPU 开销
"dynamic_partition.enable" = "true", -- 开启动态分区回收
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-30", -- 保留最近 30 天数据
"dynamic_partition.end" = "3", -- 预创建未来 3 天分区
"dynamic_partition.create_history_partition" = "true"
);
6.2 关键配置决策逻辑
| 配置项 | 本次选择背后的原因 |
|---|---|
| 数据模型 | Duke Key 模型,保留全量用户行为明细,便于后续任意维度分析 |
| 分区策略 | Auto Partition + 动态分区回收,T+0 的实时数据自动落盘,30 天内热数据优先,过期自动清理 |
| 分桶策略 | Hash 分桶 + BUCKETS AUTO,user_id 高基数列保证数据均匀,Doris 自动推算分桶数 |
| 副本数 | 3 副本,保障生产环境高可用 |
| 存储介质 | SSD 确保热数据的实时查询性能 |
| 压缩方式 | LZ4 在压缩比和查询效率之间取得良好平衡 |
6.3 核心避坑清单
-
模型不可逆:建模时先问自己“需要追溯原始值吗?能接受预聚合吗?”再决定模型
-
分区字段优先级:尽量选择业务中的高频过滤字段,通常为时间;先分区后分桶,不可倒置
-
分桶数量:请遵循“Bucket = (1G~10G)/分区”的黄金基准
-
副本≠高枕无忧:副本只保证可靠性,不保证容量溢出,仍需监控磁盘使用率
-
大表运维:
TRUNCATE PARTITION比DELETE更高效;定期清理过期分区降低元数据压力
七、结语
Apache Doris 强大的 MPP 引擎在海量数据的场景下性能斐然。而真正发挥其能力的关键,就藏在其建表语句中的模型选型、分区颗粒度、分桶算法这三把钥匙上。好的 Schema 让 Doris“飞”,坏的 Schema 让 Doris“跪”。
如果大家在自己的项目实践中遇到具体的分区分桶设计难点,欢迎在评论区分享讨论。
📚 本文参考官网 Apache Doris 4.x 及 2.1 文档实践总结,最新功能更新请以官网为准:https://doris.apache.org/
欢迎点赞、评论、转发,让更多人掌握 Doris 建表的正确“姿势”!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)