一、引言:为什么建表是 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.startend 参数控制保留的过往分区数和未来预创建分区数,实现分区的 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 且分区列必须是 DATEDATETIME 类型

  • 动态分区与跨集群复制功能不兼容

  • 小表(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)的选择原则

分桶键的选择直接影响数据分布和查询性能,应遵循以下原则:

  1. 高基数:选择值分布均匀的列(如 user_id, order_id),避免 Gender、Status 等低基数列引起数据倾斜

  2. 高频查询列:分桶键常作为查询的 WHERE 过滤条件,可利用 桶裁剪(Bucket Pruning) 快速定位

  3. 多分桶键的权衡

    • 多列分桶 → 数据更均匀,但查询需匹配所有桶列才能裁剪,适合高吞吐低并发场景

    • 单列分桶 → 点查可仅扫描一个桶,适合高并发点查场景

  4. 模型限制: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 核心避坑清单

  1. 模型不可逆:建模时先问自己“需要追溯原始值吗?能接受预聚合吗?”再决定模型

  2. 分区字段优先级:尽量选择业务中的高频过滤字段,通常为时间;先分区后分桶,不可倒置

  3. 分桶数量:请遵循“Bucket = (1G~10G)/分区”的黄金基准

  4. 副本≠高枕无忧:副本只保证可靠性,不保证容量溢出,仍需监控磁盘使用率

  5. 大表运维TRUNCATE PARTITIONDELETE 更高效;定期清理过期分区降低元数据压力

七、结语

Apache Doris 强大的 MPP 引擎在海量数据的场景下性能斐然。而真正发挥其能力的关键,就藏在其建表语句中的模型选型、分区颗粒度、分桶算法这三把钥匙上。好的 Schema 让 Doris“飞”,坏的 Schema 让 Doris“跪”

如果大家在自己的项目实践中遇到具体的分区分桶设计难点,欢迎在评论区分享讨论。

📚 本文参考官网 Apache Doris 4.x 及 2.1 文档实践总结,最新功能更新请以官网为准:https://doris.apache.org/


欢迎点赞、评论、转发,让更多人掌握 Doris 建表的正确“姿势”!

 

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐