一、引言
StarRocks 的表设计不是一个孤立的 DDL 选择题,而是围绕数据特征、写入方式、查询收益建立的一套物理设计方法。
表类型决定写入语义:明细表适合追加型原始日志,聚合表适合预聚合指标,更新表适合唯一键覆盖但已逐步被主键表替代,主键表则面向实时 CDC 与频繁更新场景,并通过 Delete+Insert、主键索引和 DelVector 避免读时多版本 Merge,从而保持实时更新下的分析性能。
数据分布决定数据能不能被少扫、均匀扫、并行扫。StarRocks 使用"分区 + 分桶"的两层机制:分区更偏向数据裁剪、TTL 与冷热管理,分桶更偏向数据均衡和执行并发;从 v3.1 起明细表支持默认随机分桶,从 v2.5.7 起支持自动设置分桶数量,从 v4.1 起还引入了默认关闭的范围分桶能力。
索引体系决定扫描阶段能否快速跳过无关数据。前缀索引由排序键自动生成,每 1024 行形成一个逻辑 Data Block 并记录首行排序列前缀;ZoneMap 利用 Min/Max/Null 统计过滤数据块;Bitmap、Bloom Filter、N-Gram Bloom Filter、倒排索引和向量索引则面向不同过滤与检索场景。

这条链路也解释了为什么"换一种表类型"往往比"加一个索引"影响更大:表类型改变的是写入和读取的基础语义,索引只是让已确定的物理布局在扫描时少读一些数据。
二、表类型选择
StarRocks 常见表类型可以理解为四种写入语义:追加明细、写入时聚合、唯一键覆盖、主键实时更新。它们的查询收益不是凭空出现的,而是来自写入阶段是否提前排序、聚合、替换或维护主键索引。
| 表类型 | 适配数据特征 | 写入方式 | 查询收益 | 主要代价 / 注意点 |
|---|---|---|---|---|
| 明细表 Duplicate / Detail | 日志、行为明细、时序明细;历史数据不更新,只追加 | 导入数据原样保留;重复行会被视为多行 | 保留最细粒度,查询灵活;排序键命中时可利用前缀索引 | 不做预聚合,聚合查询仍需扫描足够明细;不支持修改历史数据 |
| 聚合表 Aggregate | 固定维度统计、指标汇总;不需要查询原始明细 | 相同聚合键的数据在导入、Compaction、查询阶段多次聚合 | 提前减少数据量,大聚合查询收益明显 | 维度设计过细会削弱聚合效果;导入更新需包含所有列 |
| 更新表 Unique | 唯一键最新状态;订单状态等实时更新场景 | 相同唯一键的 value 列按 REPLACE 语义返回最新版本 | 支持唯一键覆盖,适合最新状态分析 | 读时需要聚合多版本;官方说明其已逐渐被主键表替代 |
| 主键表 Primary Key | CDC、事务库同步、频繁 Update/Delete、宽表部分列更新 | 主键唯一非空;冲突时新数据替代旧数据;通过 Delete+Insert、主键索引、DelVector 维护最新行 | 实时更新下仍能保持复杂即席查询性能;相对更新表可提升 3~10 倍查询性能 | 主键必须包含分区列和分桶列;主键不可修改;需要关注主键索引内存/磁盘开销 |
1.明细表:不要过早丢失事实粒度
明细表是 StarRocks 默认表类型,适合原始日志、原始操作记录、时序数据等追加写场景。官方明确指出,明细表支持追加新数据,不支持修改历史数据;如果导入两行完全相同的数据,也会按两行保存。
对于实时计算工程师,明细表常用于 ODS/DWD 层或需要灵活 ad-hoc 分析的宽事件表。此时不要急于把所有查询都固化成聚合表,因为一旦预聚合维度设计不完整,后续排查和探索性分析会失去原始事实。

2.聚合表:用写入成本换查询成本
聚合表要求定义聚合键,并为值列指定聚合函数;当多行具有相同聚合键时,值列会被聚合。官方文档说明,聚合表在数据摄取、后台 Compaction、查询阶段都会进行聚合,从而减少查询时需要处理的数据量。
它适合"查询维度稳定、指标口径明确、基本不查明细"的报表类场景。例如按天、城市、渠道统计 PV、UV、GMV 等。工程上要避免两个误区:第一,不要把过高基数的随机 ID 放入聚合键导致聚合率接近 0;第二,不要在仍需回溯明细的场景中过早使用聚合表。

3.主键表:实时 CDC 的首选模型
主键表的核心价值是同时支撑实时更新和复杂即席查询。官方文档说明,主键表采用 Delete+Insert 策略,借助主键索引和 DelVector 保证查询只读取同一主键下的最新数据,避免 Merge-On-Read 多版本合并,并使谓词和索引可以下推到底层数据。
这使它特别适合 MySQL Binlog / Flink CDC 同步、订单宽表、用户画像宽表、多流部分列更新等场景。主键表不是"更高级的更新表"这么简单,它改变了更新数据的读取路径:从读时合并,转向写入提交阶段维护删除标记与主键位置映射。

4.更新表:能用,但优先考虑主键表
更新表通过唯一键建模,相同唯一键的数据会按 REPLACE 语义返回最新值。官方文档同时指出,更新表能够支撑实时频繁更新场景,但目前已经逐渐被主键表代替。
更新表的典型限制在于读时需要处理多版本数据。如果导入频率过高,版本过多会拖累查询性能;官方也建议导入频率以满足业务实时性为准,而不是盲目秒级导入。对于新系统选型,除非存在历史兼容约束,一般应优先评估主键表。
三、数据分布设计
StarRocks 的数据分布是两层结构:第一层是分区,第二层是分桶。官方文档明确说明,建表时可通过合理设置分区和分桶实现数据均匀分布和查询性能提升,查询时能够有效裁剪扫描量并利用集群并发能力。
sql
Table: orders
|
+-- Partition p20260801 <--- 分区:按时间/枚举裁剪、TTL、冷热管理
| +-- Tablet bucket-0 <--- 分桶:并行扫描、数据均衡、局部聚合
| +-- Tablet bucket-1
| +-- Tablet bucket-2
|
+-- Partition p20260802
+-- Tablet bucket-0
+-- Tablet bucket-1
+-- Tablet bucket-2
1.分区:先问"能不能少扫一整片数据"
分区适合承载数据管理和粗粒度裁剪:时间分区用于按天/月裁剪和 TTL,枚举分区用于国家、城市、租户等管理场景。官方文档推荐表达式分区,称其适用于大多数场景,并从 v3.4 开始进一步统一分区策略,未来会逐渐取代其他分区策略。
实时 OLAP 中最常见的实践是按事件时间或业务日期做表达式分区。分区粒度要和查询与生命周期一致:如果查询常按天过滤且数据按天过期,就按天;如果月数据量小且查询多为月级汇总,按月能减少元数据管理成本。
2.分桶:再问"扫的时候能不能均匀并发"
分桶把每个分区拆成多个 Tablet。StarRocks 支持随机分桶、哈希分桶,以及 v4.1 起默认关闭的范围分桶;其中随机分桶从 v3.1 起在建表和新增分区时可不设置分桶键,哈希分桶则要求指定分桶键。
随机分桶适合明细追加表、查询过滤不强依赖某个 Join/Group By 局部性的场景,优点是简单且降低分桶键选择成本。哈希分桶适合存在稳定高频过滤、Join 或聚合键的场景,优点是同一分桶键值会唯一落到对应分桶,有利于局部性,但如果分桶键倾斜会带来 Tablet 热点。
| 分布方式 | 适配数据特征 | 写入影响 | 查询收益 | 建议 |
|---|---|---|---|---|
| Random | 追加明细、过滤模式不稳定、无需按键局部化 | 无需选择分桶键,写入更简单 | 数据均匀性较好,但无法利用同键局部性 | 新建明细表可优先从默认随机分桶开始 |
| Hash | 有稳定过滤列、Join Key、Group By Key,且基数较高 | 需要选择分桶键;相同键落同一分桶 | 提高局部聚合/Join/过滤效率,提升并发 | 避免低基数或严重倾斜字段单独做分桶键 |
| Range + Hash | 时间序列 + 稳定业务键,如订单日期 + 商户 | 分区和分桶都需设计 | 先按时间裁剪,再按键并行扫描 | 实时数仓事实表最常见组合 |
| List + Hash | 枚举域管理 + 稳定业务键,如国家/城市 + 用户 | 需维护枚举分区策略 | 枚举裁剪与哈希并发兼顾 | 适合明确地域、租户、业务线隔离的数据 |
| Range 分桶 | 按表键或排序键范围组织数据 | v4.1 起,需开启 FE 配置 | 同一 Tablet 存连续范围,支持自动分裂/合并缓解倾斜 | 当前建议谨慎评估版本与配置,见不确定性说明 |
四、索引设计
StarRocks 的索引目标不是把每个查询都变成点查,而是让 Scan 节点尽可能少读无关数据。官方文档把索引分为内置索引和手动创建索引:内置索引包括前缀索引、Ordinal 索引、ZoneMap 索引;手动索引包括 Bitmap、Bloom Filter、N-Gram Bloom Filter、倒排索引和向量索引等。

1.排序键与前缀索引:最重要的"隐式索引"
前缀索引在写入过程中自动生成:数据按排序键排序后,每写入 1024 行构成一个逻辑 Data Block,前缀索引表记录该块第一行排序列组成的前缀;查询过滤条件命中前缀时,可快速定位数据并减少扫描量。官方还说明,前缀索引是稀疏索引,大小至少比数据量小 1024 倍,通常可全量缓存在内存中。
排序键设计的关键不是"列越多越好",而是"最常用过滤条件是否构成前缀"。官方建议排序列一般为 3 个,建议不超过 4 个;如果多个过滤列频率接近,再结合基数和压缩率取舍。
ini
ORDER BY (dt, merchant_id, event_type)
命中较好:where dt = '2026-08-01'
命中更好:where dt = '2026-08-01' and merchant_id = 9527
命中最佳:where dt = '2026-08-01' and merchant_id = 9527 and event_type = 'pay'
命中较差:where merchant_id = 9527 -- 未包含最左前缀 dt
2.ZoneMap:范围过滤的低成本收益
ZoneMap 存储每块数据的 Min、Max、HasNull、HasNotNull 等统计信息。查询时,如果过滤条件可根据这些统计判断整块数据不可能命中,就可以直接跳过,从而减少扫描量。
ZoneMap 对时间、数值范围、排序后局部性较好的列尤其有效。它不要求用户显式创建,但它的效果会被排序键放大:相似值越集中,Min/Max 范围越窄,可跳过的数据块越多。
3.Bitmap 与 Bloom Filter:非前缀列的专项补强
当查询条件列不是前缀字段时,可以考虑手动索引。官方说明 Bitmap 索引适用于较高基数列查询和多个低基数列组合查询,且过滤效果较好时至少可以过滤掉 999/1000 的数据;Bloom Filter 适合 ID 等高基数列,但存在一定误判率。
工程上可以这样理解:Bitmap 更适合"集合过滤"和多个条件组合,Bloom Filter 更适合"高基数等值判断是否可能存在"。如果过滤选择性不强,索引维护成本可能大于查询收益。
4.N-Gram、倒排、向量索引:面向新型分析检索
N-Gram Bloom Filter 用于加速 LIKE 查询或 ngram_search 相关函数;全文倒排索引用于关键词匹配;向量索引用于近似最近邻搜索(ANNS)。这些能力说明 StarRocks 的表设计正在从传统 OLAP 扫描优化,扩展到文本检索与向量检索一体化。
五、最佳实践
面向实时 OLAP 表设计,可以用一套自上而下的问题清单替代拍脑袋写 DDL。
vbnet
Step 1:判断写入语义 → 追加明细选明细表;固定维度预聚合选聚合表;CDC/Update/Delete 优先主键表;历史兼容再考虑更新表。
Step 2:判断裁剪维度 → 高频时间过滤或 TTL 用表达式分区;枚举管理用表达式或 List 分区;分区粒度与查询粒度、过期粒度一致。
Step 3:判断并发与局部性 → 明细默认可用随机分桶;稳定高基数过滤/Join/Group By 键考虑哈希分桶;警惕低基数倾斜。
Step 4:判断排序键 → 把最高频过滤列放前面,确保查询条件能命中前缀;排序列通常 3 个左右,不盲目堆列。
Step 5:判断手动索引 → 非前缀列且高选择性过滤再建 Bitmap/Bloom;LIKE、全文、向量检索使用对应专项索引。
示例:实时订单宽表
sql
CREATE TABLE dwd_order_wide (
order_id BIGINT NOT NULL,
dt DATE NOT NULL,
merchant_id BIGINT NOT NULL,
user_id BIGINT,
order_state TINYINT,
pay_amount DECIMAL(18,2),
update_time DATETIME
)
PRIMARY KEY (order_id, dt, merchant_id)
PARTITION BY date_trunc('day', dt)
DISTRIBUTED BY HASH(merchant_id)
ORDER BY (dt, merchant_id)
PROPERTIES (
"enable_persistent_index" = "true"
);
这个设计的逻辑是:订单来自 CDC,存在状态更新和删除,所以选择主键表;按 dt 分区用于时间裁剪和生命周期管理;按 merchant_id 哈希分桶用于商家维度分析的局部性;排序键使用 dt, merchant_id,服务"某天某商家订单分析"的高频查询。