Day 8|表设计四件套:排序键、分区、分桶与副本

Day 7 解决了一个基础问题:相同业务 Key 再次写入时,Doris 应该保存全部明细、按照聚合规则合并,还是维护一条当前状态。今天继续向下走一层,讨论数据在集群中的实际组织方式。
一张表创建完成以后,数据会持续写入很多年。查询习惯会逐渐稳定,数据量会从 GB 增长到 TB、PB,节点会扩容,磁盘会发生故障,历史数据会进入冷存储。排序键、分区、分桶、副本和 Tablet 数量,会在整个生命周期里持续影响以下结果:
- 一条查询需要扫描多少数据;
- 多个 BE 能否同时参与计算;
- 写入任务会生成多少 Rowset 与 Segment;
- FE 需要维护多少元数据;
- Compaction、Balance 和副本修复需要承担多少工作;
- 单机、磁盘或机房发生故障时,数据还能否继续服务;
- 未来调整结构时,需要一次轻量变更,还是完整迁移。
表结构设计因此是一项长期工程决策。一个参数看起来只占 DDL 中的一行,后续可能影响几十亿行数据和数百台机器。
本日学习目标
完成本篇后,你应当能够:
- 解释
Table → Partition → Tablet → Replica → Rowset → Segment的层级关系; - 说明排序键、Key 列和前缀索引之间的联系;
- 根据查询窗口、增长速度和数据留存周期选择分区粒度;
- 区分 Range、List、Dynamic Partition 与 Auto Partition;
- 根据等值过滤、Join Key、数据基数和写入模式选择 Hash 或 Random 分桶;
- 估算 Bucket、Tablet 与 Replica 数量,并识别元数据膨胀风险;
- 使用
replication_num与replication_allocation设计生产副本策略; - 通过
EXPLAIN、SHOW PARTITIONS、SHOW TABLETS和副本状态命令验证设计结果; - 完成三类典型业务表的 DDL,并形成可评审的设计说明。
实验前提
本篇沿用 Day 5 的单 FE、单 BE 学习环境。实验表统一设置
replication_num = 1。生产集群通常使用三副本或符合企业容灾要求的标签化副本策略。
版本口径
截至 2026-08-16,Apache Doris 官网将 4.1.3 标记为 Latest,将 4.0.8 标记为 Stable。本文以 4.x 通用能力为主。Storage Format V3 等 4.1 能力会单独标明版本范围。
资料口径说明
原培训材料第 111、117、118 页建立了“分区负责管理、分桶负责物理分布”的基础框架,并给出了 Range/List、动态分区和 Bucket 初始估算方法。当前 4.x 官方文档新增或强化了 Auto Partition、Random Bucketing、
BUCKETS AUTO、Tablet 元数据上限和更完整的分桶建议。本文保留原有教学主线,同时使用当前官方口径更新参数与边界。
一、表结构设计需要完成五个相互联动的决定

Doris 建表时最容易被忽略的一点,是各项设计并不独立。一个字段可以同时参与数据模型、排序、分区和分桶,但它在每个位置承担的职责不同。
1. 排序键:决定 Tablet 内部怎样排列
Doris 的 Segment 文件内部按排序键组织数据。合理的排序顺序能够形成更好的数据局部性,让前缀索引、ZoneMap、压缩、范围过滤和局部聚合发挥作用。
排序键回答的问题是:在同一个 Tablet 内,哪些行应该彼此靠近。
2. 分区:决定数据的管理边界
分区通常依据日期、时间或有限枚举值划分。每个分区可以独立创建、删除、归档、设置存储策略,也可以在查询规划阶段被整体裁剪。
分区回答的问题是:哪些数据可以作为一个整体管理和跳过。
3. 分桶:决定数据怎样展开到多个 Tablet
每个分区继续被切成若干 Bucket,每个 Bucket 对应一个 Tablet。分桶数量决定了单分区的并行扫描上限,也影响数据在 BE 之间的均衡程度。
分桶回答的问题是:一个分区需要多少个并行工作单元,以及每一行应该进入哪个单元。
4. 副本:决定故障后的服务能力
每个 Tablet 可以拥有多份 Replica。副本分布在不同 BE、磁盘或资源标签上,后台调度器负责健康检查、修复和均衡。
副本回答的问题是:某台机器或某块磁盘失效以后,哪份数据继续提供服务。
5. 表属性:决定长期运行方式
表属性会控制副本数、更新模式、存储格式、压缩、冷却策略、动态分区、Bloom Filter 和其他行为。属性数量很多,生产环境需要保持克制:每个属性都应对应明确的业务目标和验证证据。
6. 推荐的设计顺序
一套更稳定的设计顺序如下:
- 明确数据模型和相同 Key 的写入语义;
- 收集真实查询条件、Join 关系、TopN 排序和数据留存要求;
- 选择管理边界,也就是分区字段与粒度;
- 选择物理分布方式,也就是分桶列与 Bucket 数量;
- 设计排序键,让分区内部的数据布局贴近高频查询;
- 按可用性目标设置副本和故障域;
- 增加确有必要的表属性与索引;
- 使用真实数据、真实并发和真实写入节奏完成验证。
这套顺序可以避免两个常见问题:先按照经验写 DDL,随后强迫业务查询适应表结构;或者只追求单条 SQL 的最快结果,忽略写入、元数据、Compaction 和容灾成本。
二、先看清物理层级:Table、Partition、Tablet 与 Replica

从业务用户视角看,Doris 中只有数据库和表。进入存储层以后,一张表会被展开成多级对象:
Database
└── Table
├── Partition 1
│ ├── Tablet 1
│ │ ├── Replica on BE-1
│ │ ├── Replica on BE-2
│ │ └── Replica on BE-3
│ ├── Tablet 2
│ └── ...
├── Partition 2
└── ...
每个 Tablet 内继续包含多个 Rowset;每次导入事务或 Compaction 会形成新的 Rowset;Rowset 下面的 Segment 是实际列式文件。
1. Partition 是逻辑管理单元
一行数据先根据分区表达式找到唯一分区。Range 分区通过范围边界定位,List 分区通过枚举集合定位。查询到来时,FE 根据谓词裁剪无关分区,剩余分区才会进入后续调度。
没有显式写 PARTITION BY 时,Doris 会为表创建一个对用户透明的默认分区。小型维表可以使用默认分区;持续增长的大表通常需要显式规划生命周期。
2. Bucket 与 Tablet 一一对应
一个分区拥有 N 个 Bucket,也就拥有 N 个基础 Tablet。Hash 分桶根据分桶列计算哈希值,Random 分桶按随机或批次策略分配。Tablet 是数据迁移、复制、平衡、修复和并行扫描的重要单位。
3. Replica 是 Tablet 的物理副本
三副本表中,一个 Tablet 通常有三份 Replica,放在不同 BE 上。查询调度会选择健康副本参与扫描;后台 TabletChecker 和 TabletScheduler 持续检查健康状态,并在副本缺失时触发修复。
4. 数量如何计算
只看基础索引时:
基础 Tablet 数 = 分区数 × 每分区 Bucket 数
物理 Replica 数 = 基础 Tablet 数 × 副本数
如果表拥有同步 Rollup 或同步物化索引,每个索引也需要对应的 Tablet 与副本,实际数量会继续增加。因此,评审 Bucket 数时需要同时查看分区数、同步索引数和副本数。
举例:
12 个按月分区 × 32 Buckets = 384 个基础 Tablets
384 Tablets × 3 副本 = 1,152 个物理 Replicas
再增加两个同步 Rollup 后,Tablet 与 Replica 的总规模会继续放大。FE 要维护更多元数据,BE 要处理更多版本、文件、Compaction 与修复任务。
5. Tablet 数量为什么需要控制
当前 4.x 官方文档给出了一组工程边界:单个 BE 承载的 Tablet 数建议低于 20,000;每分区 Bucket 数通常建议低于 128;单 Tablet 数据量常以 1–10 GB 为经验目标。极大规模集群需要结合硬件、表数量、索引数量和写入节奏重新测算。
Tablet 太少会带来以下问题:
- 大表集中在少量 BE,其他节点空闲;
- 单查询并行度不足;
- 单 Tablet 文件和 Compaction 负担过重;
- 扩容后新节点难以充分参与既有查询。
Tablet 太多同样会产生代价:
- FE 元数据量持续上升;
- 每次小批写入分散到大量 Tablet,生成更多小 Rowset;
- Compaction、Balance 与副本修复任务增多;
- 建表、Schema Change 和集群恢复时间变长;
- 高并发写入需要打开更多文件和连接。
表设计的目标是让 Tablet 数量与业务规模匹配,保留合理并行度,同时控制长期运维成本。
三、排序键:先安排 Tablet 内部的数据局部性

Doris 将数据按 Sort Key 有序存储。三种数据模型的排序键来源不同:
| 数据模型 | 排序键来源 |
|---|---|
| Duplicate Key | DUPLICATE KEY(...) 中的列 |
| Aggregate Key | AGGREGATE KEY(...) 中的列 |
| Unique Key | UNIQUE KEY(...) 中的列 |
Key 在 Day 7 中已经承担了明细、聚合或主键语义;今天需要继续考虑它的物理排序效果。
在 Doris 建表语法中,Key 列需要位于字段列表前部,字段顺序应与 Key 定义保持一致。Value 列放在 Key 列之后。这项规则既影响 DDL 是否能创建,也让排序语义在 Schema 中保持清晰。
1. 前缀索引怎样工作
Doris 会自动基于排序键构建稀疏 Prefix Index。每个逻辑数据块保留一条索引项,索引内容来自该数据块第一行排序键的前缀,最长为 36 字节。索引规模较小,可以常驻内存,查询通过二分定位快速找到可能命中的数据块。
前缀索引适合:
col = value;col IN (...);col > value、BETWEEN等范围查询;- 从排序键最左侧开始连续命中的组合条件。
例如排序键为:
(event_date, user_id, event_type, event_time)
以下条件可以较好地利用前缀:
WHERE event_date = '2026-08-16'
AND user_id = 1001
只写 WHERE user_id = 1001 会跳过最左侧的 event_date,前缀导航能力明显减弱。分区裁剪可能已经缩小日期范围,但 Tablet 内的排序仍需通过真实 Profile 验证。
2. 36 字节与 VARCHAR 截断规则
前缀索引累计排序列的前 36 字节。遇到 VARCHAR 时会在该列结束位置截断,后续列不再进入前缀索引。第一列就是短 VARCHAR 时,即使没有用满 36 字节,后面的整型或时间列也无法进入这一组前缀。
因此,常见设计建议包括:
- 高频等值或范围过滤列靠前;
- 固定长度的
BIGINT、INT、DATE、DATETIME优先于长字符串; - 宽
VARCHAR不宜过早出现; - 排序列数量保持克制,通常围绕最核心的两到四个过滤方向设计;
- 时间分区已经完成粗裁剪后,排序键可以进一步优化分区内部的服务名、用户、地区或事件时间过滤。
3. 低基数列应该放在前面吗
答案取决于查询组合。低基数列放在前部可以让同类数据聚集,提高压缩和局部扫描效果;它的选择性有限,单独过滤时仍可能读取大量数据。高基数列定位更精确,但数据写入和 Join 设计也需要同步考虑。
评审时可以用三个问题判断:
- 该列出现在多少比例的查询中?
- 它通常单独使用,还是与左侧列一起使用?
- 过滤后预计保留多少行?
4. 一张表只有一组排序键
当业务存在多套完全不同的高频过滤路径时,可以考虑:
- 为其他列增加倒排索引;
- 创建列顺序不同的同步物化视图;
- 为隔离明显的工作负载建立独立服务表;
- 通过异步物化视图形成面向应用的汇总层。
5. 如何验证排序键效果
使用 EXPLAIN 只能看到整体 Scan 结构,最终效果应结合 Query Profile。Prefix Index 相关指标中,RowsKeyRangeFiltered 可以反映排序键范围过滤的贡献。还要同时观察:
RowsRead与RowsReturned;- ZoneMap、Bloom Filter、倒排索引分别过滤了多少行;
- Scan 的 I/O、解压和 CPU 时间;
- 同一查询在冷缓存与热缓存下的差异。
四、分区:围绕查询窗口与生命周期划定边界

分区是粗粒度的数据管理机制。它最适合表达稳定、可解释、可批量管理的边界,例如日期、月份、地区、业务线和有限租户组。
1. Range Partition
Range 按连续范围划分,时间数据最常使用。一个月一个分区的示例:
PARTITION BY RANGE(order_date) (
PARTITION p202607 VALUES ["2026-07-01", "2026-08-01"),
PARTITION p202608 VALUES ["2026-08-01", "2026-09-01"),
PARTITION p202609 VALUES ["2026-09-01", "2026-10-01")
)
它适合:
- 订单、行为、日志、指标等随时间增长的事实数据;
- 查询普遍带有明确时间范围;
- 需要按天、月或季度删除历史数据;
- 冷热分层和备份策略与时间相关。
2. List Partition
List 按枚举值集合划分:
PARTITION BY LIST(region) (
PARTITION p_cn VALUES IN ("CN"),
PARTITION p_us VALUES IN ("US"),
PARTITION p_eu VALUES IN ("DE", "FR", "IT", "ES")
)
它适合有限且稳定的离散维度。地区、业务线或少量监管域可以采用 List。用户 ID、设备 ID、订单 ID 等高基数值不适合作为一值一分区,否则分区数量会快速失控。
3. 分区粒度怎样确定
分区粒度需要同时考虑四组数据:
查询窗口
多数查询看最近一天,按天分区有利于准确裁剪;多数查询按月汇总,月分区通常更简洁。小时级分区只适合吞吐极高、查询窗口很短、留存策略也按小时执行的场景。
单分区数据量
分区太小会增加分区元数据和写入分散度;分区太大会导致删除、迁移、备份和扫描范围过粗。可以先估算压缩后体积,再结合 Bucket 数量控制单 Tablet 大小。
写入集中度
一次导入同时触达大量历史分区,会在许多 Tablet 上产生小 Rowset,放大 Compaction 压力。实时流通常集中写当前少量分区;大范围历史回补应控制批次和分区跨度。
生命周期
保留 30 天日志、保留 25 个月经营数据、保留 7 年财务明细,对应不同分区粒度和归档策略。删除过期分区属于元数据操作,成本远低于在大表上逐行执行条件删除。
4. 分区列的约束
分区列必须属于 Key 列。Unique Key 表中,分区列还必须是 Unique Key 的子集,否则同一主键可能进入不同分区,破坏去重语义。
这项约束会影响“当前状态表”的设计。订单主键只有 order_id 时,直接按 create_date 分区会迫使日期进入 Unique Key,主键语义随之变化。常见处理方法包括:
- 当前状态表保持单分区,通过合理分桶承载;
- 业务主键天然包含租户或日期时,将这些字段纳入完整主键;
- 历史事件进入按时间分区的 Duplicate 表,当前状态进入独立 Unique 表;
- 超大状态表在 POC 中验证分区、主键和更新语义后再落地。
5. 分区裁剪怎样验证
查询条件尽量直接作用于分区列:
WHERE event_time >= '2026-08-16 00:00:00'
AND event_time < '2026-08-17 00:00:00'
复杂函数、隐式类型转换和非等价表达式可能降低裁剪能力。运行:
EXPLAIN SELECT ...;
关注 Scan 节点中的:
partitions=1/365
它表示 365 个分区只保留了 1 个。实际字段名称随版本和 Explain 模式有所差异,判断原则保持一致:扫描分区数应符合业务时间范围。
五、Dynamic Partition 与 Auto Partition

两种能力都能自动创建分区,驱动逻辑不同。
1. Dynamic Partition:按照当前时间滚动
Dynamic Partition 由调度器周期执行,根据当前日期和配置偏移量创建未来分区、删除过期分区。它适合实时连续写入和固定时间窗口管理。
PROPERTIES (
"dynamic_partition.enable" = "true",
"dynamic_partition.time_unit" = "DAY",
"dynamic_partition.start" = "-30",
"dynamic_partition.end" = "3",
"dynamic_partition.prefix" = "p",
"dynamic_partition.buckets" = "16"
)
以上配置表达:保留过去 30 天的时间窗口,并预创建未来 3 天分区。具体保留边界、历史分区数量和系统参数应在目标版本中验证。
Dynamic Partition 的主要限制包括:
- 只支持
DATE/DATETIME上的 Range 分区; - 只支持单分区键;
- 它围绕系统当前时间推进,历史回补和离散枚举值并不自然;
dynamic_partition.buckets会覆盖DISTRIBUTED中的 Bucket 数,需要避免两处配置不一致。
2. Auto Partition:按照实际写入值创建
Auto Partition 在写入时检查分区值。目标分区不存在时,Doris 自动创建它。
AUTO PARTITION BY RANGE (
date_trunc(event_time, 'day')
) ()
Auto Range 支持 DATE、DATETIME;Auto List 可按照一个或多个枚举列创建分区。它适合:
- 历史数据回补;
- 数据日期与当前时间无关;
- 分区值难以提前枚举;
- 离散租户、地区或业务值按需出现;
- 手工创建分区的工作量过大。
当前 4.x 官方文档将 Auto Partition 作为自动分区管理的推荐主线,并将其视为 Dynamic Partition 的后续方案。Dynamic 的 TTL 与未来分区预创建仍然具备明确价值。
3. 生命周期优先使用 retention_count
技术上,Auto Partition 与 Dynamic Partition 仍可组合;当前 4.x 官方文档已经不再推荐新项目采用这条组合。AUTO RANGE 表可以使用 partition.retention_count 保留分区值最大的 N 个历史分区,当前和未来分区继续保留。
PROPERTIES (
"partition.retention_count" = "30"
)
“保留 30 天”需要先确定当天是否计入。每天一个分区、当天计入 30 天窗口时,可以保留 29 个历史分区并加上当前分区。存在未来数据、跨时区写入和迟到数据时,还要单独核对边界。已有 Dynamic Partition 表运行稳定时,可以继续维护;迁移到 Auto Partition 前应验证回补、回收、分区命名和下游任务兼容性。
4. 自动化也需要治理
自动建分区降低了人工成本,也可能放大脏数据:
- 异常年份
2099-01-01会创建意外分区; - 空格、大小写或编码差异会生成多个 List 分区;
- 高基数租户值会导致分区爆炸;
- 回补任务可能在短时间创建大量分区和 Tablet;
- 保留策略缺失会让历史分区持续增长。
因此需要在导入前设置日期范围、枚举白名单、数据质量检查和保留规则,并为分区数量配置监控告警。
六、分桶:为数据均衡、并行扫描和 Join 协同提供基础

分区解决“读哪些时间或业务范围”,分桶解决“这些数据由多少个工作单元并行处理”。
1. Hash Bucketing
DISTRIBUTED BY HASH(user_id) BUCKETS 32
Doris 对分桶列计算哈希值并映射到一个 Bucket。Hash 适合以下情况:
- 存在高频等值过滤,如
user_id = ?、order_id IN (...); - 多张大表经常按同一 Key Join;
- 分桶列基数高、数据分布相对均匀;
- 希望通过 Bucket Pruning 只扫描少量 Tablet;
- 需要为 Bucket Shuffle Join 或 Colocate Join 建立一致布局。
当查询包含 Hash 分桶列的等值条件时,FE 可以直接计算目标 Bucket。EXPLAIN 中可能看到:
tablets=1/32
范围条件通常无法定位到单一 Hash Bucket,因为连续业务值经过哈希后会分散到不同 Tablet。
2. Random Bucketing
DISTRIBUTED BY RANDOM BUCKETS 32
Random 将数据分散到多个 Bucket,不依赖业务列。当前 4.x POC 指南将其作为 Duplicate Key 追加型表的常用选择,特别适合缺少稳定等值过滤列、写入均衡优先的日志和事件数据。
Random 的特点:
- 不会因为某个业务 Key 过热而集中到单一 Bucket;
- 不提供基于业务列的 Bucket Pruning;
- 不能依靠分桶列完成 Colocate 对齐;
- 适用范围受表模型限制,当前主要面向 Duplicate Key;
- 可结合
load_to_single_tablet优化小批写入,但需要评估数据均衡和后续查询。
3. 分桶列怎样选择
好的 Hash 分桶列通常具备:
- 较高基数;
- 分布稳定,没有少数值占据大部分数据;
- 经常出现在等值过滤或 Join 条件中;
- 写入时能够稳定获得;
- 未来业务扩展后仍能保持均匀。
需要谨慎的列包括:
- 性别、状态、渠道等低基数列;
- 少数超级客户占据大部分数据的租户列;
- 大量 NULL 或默认值的列;
- 经常发生语义变化的临时字段;
- 只在极少数查询中使用的高基数列。
复合 Hash 分桶可以改善单列倾斜,例如 HASH(tenant_id, order_id)。代价是只有完整或可推导的组合条件才能获得更精准的 Bucket Pruning,Join 对齐也需要保持相同列顺序和 Bucket 数量。
4. 分桶与 Join 的关系
两张表使用相同分桶列、Bucket 数量和 Colocate Group 时,相同 Key 的数据可以被放到同一组 BE,Join 有机会避免跨节点 Shuffle。这个策略适合稳定、高频、数据规模较大的 Join。
它也会增加约束:
- 多张表需要共同维护 Bucket 布局;
- 扩容与再平衡需要整体考虑;
- Bucket 数早期估算过小会限制长期并行度;
- 数据倾斜会同时影响多张表。
因此,Colocate 属于在真实负载证明 Join Shuffle 成本较高后使用的专项优化,不宜在所有表上默认开启。
七、Bucket 数怎样估算

原培训材料给出了“总数据量 ÷ 副本数 ÷ 1GB”的初始公式。这个公式便于课堂快速理解,但在生产设计中需要进一步修正。副本数决定物理存储总量,每份 Replica 都保存完整 Tablet;Bucket 数主要由单分区单份逻辑数据量和目标 Tablet 大小决定。
更适合 4.x 的估算流程如下。
1. 估算单分区压缩后数据量
假设订单明细每天原始数据 300 GB,Doris 压缩后约 80 GB。按天分区时,单分区压缩量约为 80 GB。
2. 选择目标 Tablet 大小
官方文档给出了两组相互补充的经验:
- 基础概念文档建议单 Tablet 约 1–10 GB;
- POC 指南建议普通表压缩后单 Bucket 不超过 20 GB,Unique Key 表不超过 10 GB。
生产初始设计可以把 5–10 GB 作为常见目标,再根据数据模型、磁盘、查询并行度和写入压力调整。
3. 得到初始 Bucket 数
初始 Bucket 数 = ceil(单分区压缩数据量 ÷ 目标 Tablet 大小)
例如:
80 GB ÷ 5 GB ≈ 16 Buckets
80 GB ÷ 10 GB ≈ 8 Buckets
4. 结合 BE 数量调整
Bucket 数通常设置为 BE 数量的整数倍,便于均匀分布。8 个 BE 的集群可以从 8、16、32 等候选值中压测。节点扩容后,后台 Balance 会重新分布副本,但原有 Bucket 数不会自动增加。
5. 检查每分区上限与长期增长
官方建议每分区 Bucket 数通常低于 128。需要更高并行度时,可以先评估:
- 分区粒度是否过粗;
- 单分区数据量是否已经达到数 TB;
- 查询是否真的能够使用更多并行度;
- 写入是否会被大量 Tablet 拖慢;
- FE 和 BE 的 Tablet 总量是否可控。
6. 使用 BUCKETS AUTO
无法准确预测 Bucket 数时,可以使用:
DISTRIBUTED BY HASH(user_id) BUCKETS AUTO
PROPERTIES (
"estimate_partition_size" = "80G"
)
FE 会结合估算分区大小、BE 数量和磁盘情况选择 Bucket 数。这个功能减少了手工猜测,estimate_partition_size 仍需尽量接近真实压缩后规模。极大表和长期高速增长场景仍应通过 POC 验证。
7. 物理容量还要乘以副本
基础数据物理容量 ≈ 压缩后逻辑容量 × 副本数
还需预留:
- Rowset 与 Segment 元数据;
- 索引文件;
- Delete Bitmap;
- Compaction 临时空间;
- Schema Change 临时副本;
- 数据增长和故障修复空间。
生产容量规划通常需要保留明显余量,避免磁盘高水位时 Balance、Compaction 和副本修复相互争抢空间。
八、副本:从机器级故障走向故障域设计

每个 Tablet 可以拥有多个副本。单 BE 实验环境只能设置一副本;生产环境常用三副本,具体数量由可用性、成本、写入吞吐和监管要求决定。
1. replication_num
PROPERTIES (
"replication_num" = "3"
)
这个属性设置默认副本数量。三副本会把同一 Tablet 的三份数据放到不同 BE 上,系统根据节点健康、磁盘状态和调度策略进行分配。
2. replication_allocation
企业可能需要跨机架、可用区、资源组或业务域放置副本。可以先给 BE 配置标签,再使用:
PROPERTIES (
"replication_allocation" =
"tag.location.az1:1,tag.location.az2:1,tag.location.az3:1"
)
FE 在建表时会检查标签下是否存在足够的 BE。资源不足时 DDL 会失败,这类失败能够提前暴露容灾布局与实际资源不匹配的问题。
3. 副本数带来的收益与代价
收益包括:
- 单 BE 或单磁盘故障时仍有健康副本;
- 查询可以选择可用副本;
- 后台可以从健康副本克隆并修复缺失数据;
- 维护和滚动升级具备更大操作空间。
代价包括:
- 存储容量按副本数放大;
- 写入需要维护多份数据;
- 网络流量和修复流量增加;
- 跨可用区部署可能增加时延与带宽费用;
- 副本越多,Tablet 元数据和调度对象也越多。
4. 副本不等同于备份
副本用于集群内高可用。误删表、错误覆盖、逻辑污染和操作失误可能同步影响所有副本。生产系统仍需要独立的备份、快照、对象存储副本或灾备集群,并定期演练恢复流程。
5. 查看副本状态
常用命令包括:
SHOW REPLICA DISTRIBUTION FROM db_name.table_name;
SHOW REPLICA STATUS FROM db_name.table_name;
SHOW TABLET <tablet_id>;
关注:
- 副本是否分布在不同 BE;
- 版本是否完整;
- 是否存在 Bad 或缺失副本;
- 某些磁盘或节点是否集中承载过多副本;
- Balance 与 Repair 是否长期积压。
九、表属性与结构演进:减少无依据的参数堆叠
表属性应服务于可描述的目标。以下几类与 Day 8 最相关。
1. 副本与存储属性
| 属性 | 作用 | 评审重点 |
|---|---|---|
replication_num |
设置副本数 | 学习环境与生产环境分开配置 |
replication_allocation |
按标签分配副本 | 资源标签、故障域和节点数量是否满足 |
storage_policy |
配置远程冷却策略 | 热冷边界、对象存储成本、冷查询延迟 |
storage_format |
选择存储格式 | 4.1 的 V3 主要面向超宽表、Variant 和远程存储 |
Doris 4.1 的 Storage Format V3 将列元数据按需加载,适合数百到数千列的宽表、Variant 表和对象存储场景。普通几十列的稳定表可以继续采用成熟默认格式,升级前应通过真实数据验证兼容性与收益。远程分层存储与 Unique Key MoW 等能力存在版本和模式限制,启用前需按目标版本逐项核对。
2. 自动分区属性
Dynamic Partition 的时间单位、开始偏移、结束偏移、前缀和 Bucket 数必须形成完整生命周期。配置时同步回答:
- 过去保留多久;
- 未来预创建多久;
- 迟到数据允许回补多远;
- 历史分区是否进入冷存储;
- 自动删除是否符合审计与合规要求。
3. 索引属性
Bloom Filter 适合高基数等值与 IN 查询;倒排索引适合多列过滤、全文和复杂搜索;前缀索引由排序键自动生成。索引会增加写入、存储和 Schema Change 成本,因此每个索引都需要对应明确 SQL 和验收指标。
4. 结构变更的边界
分区列和分桶列无法直接修改。排序 Key 的重大变化通常需要重写与重排数据。建表以后发现分区、分桶或模型选择错误,常见处理流程是:
- 新建目标表;
- 使用
INSERT INTO SELECT或外部任务回灌历史数据; - 双写或持续同步增量;
- 对账行数、金额、时间范围和关键指标;
- 回归查询与写入性能;
- 低峰切换表名、视图或应用配置;
- 保留回滚窗口,再清理旧表。
表结构评审应在首次上线前投入足够时间。后期迁移仍然可行,成本会随着数据规模和业务并发持续增长。
十、三类典型业务表怎样设计

下面使用事件明细、订单当前状态和门店日汇总三个场景串联 Day 7 与 Day 8。
场景一:用户行为事件明细
特点:追加写、数据量大、按时间和事件类型查询、需要长期追溯。
CREATE TABLE ods_user_event (
event_date DATE NOT NULL,
event_type VARCHAR(32) NOT NULL,
service_name VARCHAR(64) NOT NULL,
event_time DATETIME(3) NOT NULL,
event_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
properties VARIANT NULL
)
DUPLICATE KEY(event_date, event_type, service_name, event_time)
AUTO PARTITION BY RANGE (date_trunc(event_date, 'day')) ()
DISTRIBUTED BY HASH(user_id) BUCKETS AUTO
PROPERTIES (
"estimate_partition_size" = "80G",
"replication_num" = "3"
);
设计说明:
event_date支持按天自动分区和粗裁剪;event_type、service_name、event_time形成分区内部常用过滤顺序;user_id高基数,适合均匀 Hash,也支持用户等值查询;BUCKETS AUTO根据单日估算规模计算 Bucket 数;- Variant 承载长尾属性,核心过滤字段继续使用实体列。
如果查询很少按 user_id 等值过滤,且用户热点明显,可以对比 Random Bucketing 的写入均衡和查询表现。
场景二:订单当前状态表
特点:主键 Upsert、查询经常按订单 ID 点查、状态字段持续更新。
CREATE TABLE dwd_order_current (
order_id BIGINT NOT NULL,
tenant_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
order_status VARCHAR(32) NOT NULL,
amount DECIMAL(18,2) NOT NULL,
update_time DATETIME(3) NOT NULL
)
UNIQUE KEY(order_id)
DISTRIBUTED BY HASH(order_id) BUCKETS 64
PROPERTIES (
"enable_unique_key_merge_on_write" = "true",
"replication_num" = "3"
);
设计说明:
order_id同时承担主键、排序和分桶职责;示例中的 64 Buckets 只代表一种生产候选值,最终数量需按 BE 数、容量和并发完成 POC;- 当前状态表没有按日期分区,避免日期进入 Unique Key 后改变主键语义;
- 超大规模时需要通过 POC 验证单分区 Tablet 数、FE 元数据、部分更新和点查性能;
- 完整订单历史进入独立的 Duplicate 事件表;
- CDC 乱序由 Sequence Column 或源端版本字段治理。
如果业务主键天然为 (tenant_id, order_id),可以将两列共同作为 Unique Key,并重新评估分桶与故障隔离。
场景三:门店日经营汇总
特点:固定粒度、主要查询天/月趋势、指标可以稳定合并。
CREATE TABLE ads_store_daily (
dt DATE NOT NULL,
store_id BIGINT NOT NULL,
channel VARCHAR(16) NOT NULL,
order_count BIGINT SUM DEFAULT "0",
sales_amount DECIMAL(20,2) SUM DEFAULT "0",
max_order DECIMAL(18,2) MAX DEFAULT "0"
)
AGGREGATE KEY(dt, store_id, channel)
AUTO PARTITION BY RANGE (date_trunc(dt, 'month')) ()
DISTRIBUTED BY HASH(store_id) BUCKETS AUTO
PROPERTIES (
"estimate_partition_size" = "20G",
"replication_num" = "3"
);
设计说明:
- 月分区便于按月管理,日维度保留在 Aggregate Key 中;
store_id分桶有利于门店查询和分布均衡;SUM与MAX具备稳定可合并性;- 明细仍保留在上游事实表,汇总口径变化时可以重新生成;
- 热点报表还可以增加异步物化视图或服务层表。
十一、验证与故障定位:让设计结论有证据

表结构上线前至少完成以下验证。
1. 检查最终 DDL
SHOW CREATE TABLE db_name.table_name;
确认:
- 数据模型与 Key 顺序;
- 分区表达式和边界;
- Hash/Random 分桶方式;
- Bucket 数或 Auto Bucket 参数;
- 副本数与标签;
- 关键表属性和索引。
2. 检查分区
SHOW PARTITIONS FROM db_name.table_name;
检查分区数量、边界、数据量、Bucket 数、存储介质和最近更新时间。Auto Partition 场景还要检查异常值是否生成意外分区。
3. 检查 Tablet 分布
SHOW TABLETS FROM db_name.table_name;
观察各 Tablet 的数据量、行数、版本数和 BE 分布。可以计算最大值与最小值之比,判断倾斜程度。某个 Tablet 显著大于其他 Tablet 时,优先检查分桶列的基数和热点值。
4. 检查裁剪
EXPLAIN
SELECT SUM(amount)
FROM orders
WHERE order_date >= '2026-08-01'
AND order_date < '2026-09-01'
AND user_id = 1001;
理想情况下,Explain 能同时显示较少的 partitions 和 tablets。如果分区裁剪正常、Tablet 裁剪未发生,需要检查查询是否对 Hash 分桶列使用了等值条件。
5. 检查 Query Profile
重点观察:
RowsRead、RowsReturned;RowsKeyRangeFiltered;- Scan 的 I/O、解压和 CPU 时间;
- 每个 Instance 读取的行数是否接近;
- 是否出现一个明显慢于其他实例的 Straggler;
- Join 中的 Shuffle Bytes 和 Runtime Filter 效果;
- 冷缓存与热缓存差异。
6. 检查副本
SHOW REPLICA DISTRIBUTION FROM db_name.table_name;
SHOW REPLICA STATUS FROM db_name.table_name;
确认副本健康、版本完整、分布均匀,标签化副本是否真的落入目标故障域。
7. 四类常见故障
查询扫描大量分区
常见原因:查询条件没有直接约束分区列、分区粒度与查询窗口不匹配、隐式转换或函数表达式影响裁剪。
处理方向:改写谓词,调整分区表达式;结构已经固化且收益明显时,新建表迁移。
Tablet 严重倾斜
常见原因:低基数分桶列、热点租户、NULL 集中、复合 Key 顺序不合理。
处理方向:分析频率分布,评估高基数列、复合 Hash 或 Random Bucketing。
写入和 Compaction 压力高
常见原因:Bucket 过多、单批数据太小、一次写入触达大量分区、频繁短事务。
处理方向:合并批次、减少 Bucket、集中写当前少量分区、调整导入并发,并观察 VersionNum 与 Compaction Score。
副本长期缺失
常见原因:BE 或磁盘不可用、目标标签下节点不足、磁盘空间不足、调度队列积压。
处理方向:恢复资源,检查标签和磁盘高水位;必要时提高指定表的修复优先级。
十二、常见反模式与改进建议
反模式 1:按每个用户或租户建分区
高基数值会生成海量分区和 Tablet,FE 元数据与建表成本快速上升。多数租户隔离场景更适合 Hash 分桶、行列权限、资源组或分层表设计。
反模式 2:用状态、性别、渠道做 Hash 分桶列
低基数列只能把数据集中到少数 Bucket,无法充分利用所有 BE。可以选择用户 ID、订单 ID、设备 ID 等高基数列,或者使用 Random Bucketing。
反模式 3:所有大表固定一个 Bucket
单 Tablet 会限制并行度,并把数据集中在少量节点。小维表可以使用少量 Bucket;持续增长的事实表需要按分区规模和集群节点数估算。
反模式 4:每分区创建几百甚至上千 Bucket
小批写入会产生大量小文件,Compaction 和元数据成本显著增加。官方建议通常将每分区控制在 128 以内;达到上限前先评估分区粒度。
反模式 5:把长 VARCHAR 放在排序键第一列
前缀索引会在 VARCHAR 处截断,后续列无法进入该前缀。固定长度的高频过滤列可以放在更前面,文本检索交给倒排索引。
反模式 6:只关注平均延迟
分桶倾斜、缓存冷热和副本位置常常影响 P95、P99。验收应覆盖冷缓存、热缓存、峰值写入、读写混合和节点故障。
反模式 7:生产表使用单副本
单副本只适合开发、临时或可从源端完整重建的数据。生产主数据应根据 SLA 设置多副本,并提供独立备份。
反模式 8:把副本当成灾备
逻辑错误会同步进入所有副本。备份、跨集群复制和恢复演练仍然必需。
反模式 9:上线后再考虑分区与分桶
分区列和分桶列无法原地修改。新表迁移需要回灌、追平、对账和切换,数据规模越大,项目成本越高。
十三、本日实践任务

完整包中的 sql/day08-table-design-lab.sql 提供了可执行实验。建议按照以下顺序完成。
任务 1:建立 Hash 分桶与 Random 分桶对照表
两张 Duplicate Key 表使用相同字段、相同分区和相同 Bucket 数,只改变分桶方式。加载相同数据后对比:
- 数据在 Tablet 间的分布;
user_id = ?查询的 Tablet 裁剪;- 无用户条件的时间范围聚合;
- 小批写入与大批写入的差异。
任务 2:验证 Auto Partition
写入三个不同日期,其中一个日期远离当前时间。确认 Doris 在写入时创建对应分区,并检查异常日期的治理策略。
任务 3:验证 Unique Key 当前状态表
针对同一 order_id 连续写入多个状态,确认最终只保留一个可见版本。随后检查:
- 主键点查;
- Bucket 分布;
- 副本状态;
- 与 Duplicate 事件表的职责区别。
任务 4:验证分区与 Bucket 裁剪
分别执行:
- 只带时间范围;
- 只带 Hash 分桶列等值条件;
- 同时带时间和 Hash 等值条件;
- 对分区列套用函数的查询。
记录 Explain 中的 partitions=x/y 与 tablets=x/y。
任务 5:完成容量估算
基于你的真实业务填写:
- 每日或每月原始数据量;
- 压缩比假设;
- 单分区压缩后数据量;
- 目标 Tablet 大小;
- 初始 Bucket 数;
- 分区数量;
- 同步索引数量;
- 副本数量;
- 预计 Tablet 与 Replica 总数;
- 三年增长后的容量与元数据规模。
任务 6:输出设计评审结论
完整包提供 reference/production-table-review-template.md。评审记录至少包含:
- 业务写入语义与数据模型;
- Top 查询与过滤列频次;
- 分区字段、粒度和保留期;
- 分桶方式、分桶列和 Bucket 数;
- 排序键与前缀索引预期;
- 副本数与故障域;
- Tablet 总量和三年增长;
- Explain、Profile、倾斜和故障测试证据;
- 迁移、切换与回滚方案。
十四、Knowledge Check
1. 分区与分桶分别解决什么问题?
**答案要点:**分区负责范围裁剪、生命周期和管理;分桶负责 Tablet 分布、并行度、数据均衡和部分 Join 优化。
2. 一个分区有 32 个 Bucket、3 个副本,基础数据对应多少个 Tablet 和 Replica?
**答案要点:**32 个基础 Tablet,96 个物理 Replica。同步 Rollup 会继续增加对应 Tablet 与副本。
3. Prefix Index 为什么容易被 VARCHAR 截断?
**答案要点:**前缀索引最长取排序键前 36 字节,遇到 VARCHAR 会在该列结束处截断,后续排序列不进入这一组前缀。
4. Dynamic Partition 与 Auto Partition 的主要差异是什么?
**答案要点:**Dynamic 围绕当前系统时间周期滚动;Auto 根据实际写入值按需创建,可覆盖历史回补和离散枚举值。
5. 哪类列适合 Hash 分桶?
**答案要点:**高基数、分布均匀、经常用于等值过滤或 Join 的稳定列。
6. Random Bucketing 的主要代价是什么?
**答案要点:**无法依据业务列执行 Bucket Pruning,也不便于依赖同一分桶列做 Colocate 对齐。
7. Bucket 数为什么不能只依据 CPU 核数设置?
**答案要点:**Bucket 同时决定 Tablet 大小、写入分散度、元数据、Compaction 和副本数量,需要结合单分区压缩量、BE 数、增长和查询并行度。
8. 为什么 Unique Key 表按日期分区需要格外谨慎?
**答案要点:**分区列必须是 Unique Key 的子集;随意加入日期可能改变主键语义,导致同一业务对象落入多个逻辑主键。
9. 三副本能否替代备份?
**答案要点:**不能。副本解决集群内硬件故障,逻辑误删和错误写入会影响全部副本。
10. 怎样确认一条查询同时命中了分区裁剪和 Bucket 裁剪?
**答案要点:**查看 Explain 中 partitions=x/y 与 tablets=x/y,并结合 Profile 验证实际读取行数与 Scan 成本。
十五、本日总结
今天完成了 Doris 表结构的五项核心设计:
- 排序键安排 Tablet 内部数据顺序,为前缀索引和局部扫描建立条件;
- 分区划定管理边界,让查询能够整体跳过无关时间或枚举范围;
- 分桶把分区展开成多个 Tablet,提供均衡、并行和 Bucket 裁剪;
- 副本把 Tablet 放到不同 BE 或故障域,提供硬件故障后的服务与修复能力;
- 表属性控制长期运行行为,需要每项配置都能被业务目标和实验证据解释。
一张高质量 Doris 表可以用三句话检验:
查询先通过分区缩小范围,再通过 Tablet 与索引减少扫描;写入集中到合理数量的分区和 Bucket;副本、元数据和 Compaction 规模能够随数据增长稳定运行。
Day 9 将进入数据导入体系,系统学习 Stream Load、Group Commit、Broker Load、TVF、INSERT 等批量导入方式,并把今天设计的 Partition 与 Tablet 布局带入真实写入路径。