OLAP 数据库全面解析
- 大数据
- 数据库
- OLAP
- ClickHouse
- Doris
- StarRocks
目录
- 前置理解
- OLTP vs OLAP:两种完全相反的世界
- 行存 vs 列存
- 数据在磁盘上是怎么”摆”的?
- 为什么这个”摆法”决定了一切?
- 列存还顺手带来一个巨大的红利:压缩
- 向量化执行(Vectorization)
- 传统数据库怎么”算”?——火山模型(一次一行)
- 向量化:一次处理一”批”
- MPP 架构(分而治之)
- MPP 的本质:把大问题切成小问题,并行算,再汇总
- MPP 真正的难点:Shuffle(数据重分布)
- 预聚合 vs 现场计算(两条技术路线之争)
- ClickHouse: 把”单机极致”做到偏执的猛兽
- 一句话理解 ClickHouse 的灵魂
- ClickHouse 核心引擎:MergeTree
- 写入:永远只追加,绝不原地改
- 后台合并:把小块慢慢合成大块
- 稀疏索引:不为每行建索引,每隔 N 行建一个
- ClickHouse 的”快”到底快在哪?
- 弱点
- Apache Doris: 从 Hadoop 生态里长出来的”易用型”MPP 数仓
- Doris 的架构:极简的两种节点
- Doris 的三种数据模型
- Doris 走过的弯路与它和 StarRocks 的渊源
- StarRocks: 把”极速 + 全能”作为信仰的性能怪兽
- StarRocks 凭什么”更快”?
- 全面彻底的向量化
- 成熟的 CBO 优化器(这是它 JOIN 强的核心)
- Colocate Join
- StarRocks 的存算分离与湖仓一体
- 存算分离(Storage-Compute Separation)
- 湖仓一体(Lakehouse):直接查数据湖
- StarRocks 的”表”到底是什么: 一张表的四层切分
- 它表面是张普通 SQL 表
- 一张表被自上而下切成四层
- 把四层串起来:一条查询是怎么跑的
- 三者横向对比与选型决策
- 终极对比表
- 怎么选?给你一套决策思路
- 最后,把整份文档浓缩成一张”思想地图”
前置理解
在认识任何一个具体产品之前,我们要先回答一个问题:OLAP 数据库到底要解决什么问题?为什么不能用 MySQL?
理解了这个,后面的一切设计选择你都能自己推导出来。
OLTP vs OLAP:两种完全相反的世界
我们平时用的 MySQL、PostgreSQL 属于 OLTP(联机事务处理)。而 StarRocks 这类属于 OLAP(联机分析处理)。这两个词翻译过来都很拗口,但背后的区别非常直观:
OLTP 的典型场景:你在淘宝下了一个订单。
- 操作的是一行数据(这一笔订单)
- 要读、要写、要保证事务(钱扣了订单必须生成)
- 要求毫秒级响应,因为用户在等
- 并发极高(同时几百万人下单)
OLAP 的典型场景:老板问”过去一年,华东地区、客单价 500 以上的女性用户,每个月的复购率是多少?”
- 操作的是几亿行数据,但只关心其中几列(地区、性别、客单价、时间)
- 几乎只读,很少改
- 要求秒级响应可以接受(这是分析,不是交易)
- 并发不高(就几个分析师、几张报表)
这里就是一切的起点:OLTP 是”对少数行的频繁读写”,OLAP 是”对海量行的少数列的扫描聚合”。
一个为前者优化的系统,必然不适合后者。这不是”做得好不好”的问题,而是底层数据怎么摆放就决定了它擅长什么。
行存 vs 列存
这是整份文档最重要的一个概念。理解了它,你就理解了 OLAP 数据库一半的设计。
数据在磁盘上是怎么”摆”的?
假设有一张用户表:
| id | name | age | city |
|---|---|---|---|
| 1 | 张三 | 25 | 北京 |
| 2 | 李四 | 30 | 上海 |
| 3 | 王五 | 28 | 北京 |
行式存储(Row-based,MySQL 用的)——按”行”把数据一行一行连续摆在磁盘上:
[1,张三,25,北京] [2,李四,30,上海] [3,王五,28,北京]
└──第一行──┘ └──第二行──┘ └──第三行──┘
列式存储(Columnar,OLAP 用的)——按”列”把同一列的数据连续摆在一起:
id列: [1, 2, 3]
name列: [张三, 李四, 王五]
age列: [25, 30, 28]
city列: [北京, 上海, 北京]
为什么这个”摆法”决定了一切?
现在回到老板的问题:“统计每个城市的平均年龄。” 这个查询只需要 city 和 age 两列。
行存怎么做? 磁盘是按行连续存的,所以为了拿到 city 和 age,它必须把每一行的所有列(包括用不到的 id、name)全部读进内存,然后再从每行里挑出那两列。如果表有 50 列,你为了 2 列的查询,白白读了 48 列的数据。磁盘 I/O 被严重浪费。
列存怎么做? city 列和 age 列本身就是连续存放的。它只读这两列对应的那两块连续磁盘区域,其他 48 列的数据碰都不碰。I/O 量直接降到 1/25。
这就是 OLAP 用列存的根本原因:分析查询的特点是”列少、行多、要聚合”,而列存让你只为你真正用到的列付出 I/O 代价。
列存还顺手带来一个巨大的红利:压缩
把同一列的数据放在一起,意味着这块数据类型相同、取值相似。比如 city 列全是城市名,可能就那几十个值在重复;age 列全是 0-120 的小整数。
数据越相似,压缩率越高。常见手段:
- 字典编码:把”北京/上海/广州”映射成 0/1/2,原本几个字节的字符串变成 1 个字节的数字。
- RLE(游程编码):如果排序后有 100 万行都是”北京”,不用存 100 万次,存一个”北京 × 1000000”就行。
- 位图编码 / Delta 编码等。
行存里一行混着字符串、整数、日期,类型五花八门,压缩器很难下手。列存天然给压缩创造了完美条件。压缩率高 → 磁盘占用小 → 读取的字节更少 → 查询更快,形成正向循环。
记住这个推论链:列存 → 只读需要的列 + 高压缩 → I/O 大幅降低 → 分析查询快。这是 StarRocks / Doris / ClickHouse 三者共同的地基。
向量化执行(Vectorization)
光把数据读得快还不够,算得也要快。这就引出第二个关键思想。
传统数据库怎么”算”?——火山模型(一次一行)
老式数据库(包括早期的很多系统)用一种叫 Volcano Model(火山模型) 的执行方式:数据一行一行地在各个算子(过滤、聚合、连接)之间流动。处理完第一行,再处理第二行……
问题在哪?每处理一行,都要调用一次函数(next())。处理 1 亿行就要调用 1 亿次。函数调用本身有开销(CPU 要保存现场、跳转、恢复现场),这些”杂活”的开销甚至超过了真正的计算。CPU 大量时间花在”打杂”上,而不是干正事。
向量化:一次处理一”批”
向量化的思想很简单:别一行一行来,一次处理一批(比如 4096 行)。
火山模型: 处理1行 → 处理1行 → 处理1行 ...(1亿次函数调用)
向量化: 处理4096行 → 处理4096行 ...(约2.4万次函数调用)
函数调用次数降到几万分之一,“打杂”开销几乎消失。更关键的是,它能用上现代 CPU 的杀手锏——SIMD 指令(Single Instruction Multiple Data,单指令多数据)。一条 CPU 指令可以同时对 4 个、8 个甚至 16 个数据做同样的运算(比如同时给 8 个 age 加 1)。
列存和向量化是绝配:因为同一列的数据已经连续摆在一起、类型相同,正好可以成批塞进 CPU 的 SIMD 寄存器里批量计算。列存负责”喂数据喂得整齐”,向量化负责”吃得快”。
这就是为什么 ClickHouse、StarRocks 都强调自己是 C++ 写的向量化引擎——C++ 能精细地控制内存布局和 CPU 指令,把 SIMD 的潜力压榨干净。(后面会讲为什么 Doris 这点上走过弯路。)
MPP 架构(分而治之)
单台机器再快也有上限。当数据到了 TB、PB 级,必须多台机器一起算。这就是 MPP(Massively Parallel Processing,大规模并行处理)。
MPP 的本质:把大问题切成小问题,并行算,再汇总
假设要统计 100 亿行数据的总和,有 10 台机器:
1. 数据被切成 10 份,每台机器存 10 亿行(这叫"分片 Sharding")
2. 查询来了,协调节点把任务广播给 10 台机器
3. 每台机器各自算自己那 10 亿行的局部和(并行,互不干扰)
4. 10 个局部结果汇总成最终结果
10 台机器并行,理论上快 10 倍。这就是 MPP 的”分而治之”。
MPP 真正的难点:Shuffle(数据重分布)
求和很简单,但 JOIN 就麻烦了。假设要把”订单表”和”用户表”按 user_id 连接。订单表的某一行在机器 A,但它对应的用户可能在机器 B。要连接的两行不在同一台机器上,怎么连?
答案是 Shuffle(数据重分布):按 user_id 做哈希,把相同 user_id 的数据从各个机器通过网络搬到同一台机器上,然后再做本地连接。
这里藏着 MPP 性能的关键秘密:Shuffle 要走网络,而网络是整个系统里最慢的环节。一个 OLAP 数据库的查询优化水平高不高,很大程度上就看它能不能减少、避免 Shuffle。 后面讲 StarRocks 的优势时,你会看到它的 CBO 优化器、Colocate Join 等技术,本质都在和 Shuffle 较劲。
这也解释了一个反直觉的现象:ClickHouse 的分布式 JOIN 弱,正是因为它早期对 Shuffle 的支持不完善;而 StarRocks 的 JOIN 强,正是因为它有成熟的分布式 Shuffle 和优化器。
预聚合 vs 现场计算(两条技术路线之争)
OLAP 加速的最后一个根本思想,是关于”什么时候算”的哲学分歧。
路线 A:现场计算(Massively Parallel Scan)
查询来的时候,老老实实扫原始数据、现场算。靠列存 + 向量化 + MPP 把”现场算”做到极快。优点:灵活,任何查询都能算。代表:ClickHouse 的核心思路、StarRocks 的明细模型。
路线 B:预聚合(Pre-aggregation)
提前把可能要查的结果算好存起来。查询来了直接读现成结果。优点:极快(因为不用算了)。缺点:不灵活(没预聚合的维度查不了),且占额外存储。代表:Apache Kylin 的 Cube、StarRocks/Doris 的”聚合模型”和”物化视图”。
现代数据库(StarRocks、Doris)的聪明之处在于两条路都走:默认现场计算保证灵活性,对高频固定查询用物化视图做预聚合加速,并且自动判断查询能不能用上物化视图(叫”查询改写”)。你写一个普通 SQL,它在背后偷偷帮你用预聚合的结果,又快又不用你操心。
ClickHouse: 把”单机极致”做到偏执的猛兽
一句话理解 ClickHouse 的灵魂
ClickHouse 是一个把”单表扫描和聚合”的速度做到地球第一梯队、为此不惜牺牲很多通用性的列式数据库。
它由俄罗斯搜索引擎公司 Yandex 开发,最初是为了支撑自家的网站流量分析系统(类似 Google Analytics)。这个出身决定了它的全部性格:网站分析的典型查询就是”对一张巨大的日志宽表,按各种维度过滤、聚合、排序”——单表、扫描、聚合。所以 ClickHouse 把这件事做到了偏执的极致,而对”多表 JOIN""频繁更新”这些它出身场景里用不到的东西,就相对薄弱。
理解一个系统的最好方式,就是理解它出生时要解决什么问题。 ClickHouse 的所有优缺点都能从”它生来就是个网站日志分析引擎”这一点推导出来。
ClickHouse 核心引擎:MergeTree
ClickHouse 最核心的存储引擎叫 MergeTree(合并树),名字里的”合并”是关键。它的设计思想借鉴了 LSM-Tree(日志结构合并树),我用大白话讲清楚:
写入:永远只追加,绝不原地改
当你写入数据时,ClickHouse 不会去修改已有的文件,而是直接生成一个新的小数据块(叫 part)。再写一批,再生成一个新 part。
第1次写入 → part_1(已排序、已压缩的一小块)
第2次写入 → part_2
第3次写入 → part_3
...
为什么这样设计? 因为”顺序追加写”是磁盘最快的操作,而”随机修改”是最慢的。ClickHouse 用”只追加”换来了极高的写入吞吐——它能轻松每秒吞下几百万行。
后台合并:把小块慢慢合成大块
写多了会产生很多小 part,碎片化影响查询。于是 ClickHouse 有个后台线程,不断地把多个小 part 合并成大 part(这就是 Merge-Tree 的”Merge”)。合并时顺便做排序、去重、压缩。
关键认知:这个合并是异步、后台、最终完成的。这就解释了 ClickHouse 一个让新手困惑的特性——它的”去重""更新""删除”都是”最终一致”的,不是立刻生效的。你执行一个删除,数据可能在下一次后台合并时才真正消失。所以 ClickHouse 里有个著名的
OPTIMIZE TABLE ... FINAL语句,就是手动逼它立刻合并。
稀疏索引:不为每行建索引,每隔 N 行建一个
ClickHouse 的主键索引是稀疏的——它不像 MySQL 为每一行建索引,而是每隔 8192 行(一个”颗粒 granule”)才记录一个索引项。
为什么? 因为 OLAP 是扫描海量数据,不是精确查单行。稀疏索引足够帮你快速跳到大致位置(“你要的数据在第 800 万到 808 万行这块”),然后那一块用向量化暴力扫一遍即可。稀疏索引占用极小的内存,却能跳过大量无关数据块。这又是一个”为分析场景量身定制”的取舍。
ClickHouse 的”快”到底快在哪?
把前面的思想串起来,ClickHouse 的极致速度来自这几点叠加:
- 列存 + 极致压缩:它的压缩算法和编码做得非常激进,I/O 极省。
- 向量化 + 手写 SIMD:C++ 写的引擎,针对不同 CPU 指令集(SSE/AVX)手工优化关键算子。
- MergeTree 的数据预排序:数据按主键排好序存储,过滤和范围扫描时能大量跳过数据块。
- 能榨干单机的每一分性能:ClickHouse 在单机/小集群上的单表查询性能,至今仍是业界标杆。
弱点
理解了”它生来是单表日志分析引擎”,它的弱点你就能全部预测出来:
| 弱点 | 根本原因 |
|---|---|
| 分布式 JOIN 弱 | 出身场景是单张宽表,不需要复杂多表关联,所以 Shuffle 机制和优化器投入少。大表 JOIN 容易内存爆炸或很慢。 |
| 不擅长高频更新/删除 | MergeTree 是”只追加 + 后台合并”,更新删除是”最终一致”的笨重操作,不是为事务设计的。 |
| 运维复杂、分布式弱 | 早期 ClickHouse 的集群要靠手动配置分片和副本,自己写分布式表,不够”开箱即用”。 |
| 并发能力有限 | 它把单个查询的资源吃满来追求单查询极速,所以高并发(成百上千用户同时查)下表现一般。 |
Apache Doris: 从 Hadoop 生态里长出来的”易用型”MPP 数仓
Doris 追求的是”开箱即用的、对开发者友好的、功能完整的 MPP 分析数据库”——它不一定每项都做到极致,但它要让你用得舒服、覆盖得全。
Doris 起源于百度内部的报表分析系统(最早叫 Palo),后来捐给 Apache 基金会开源。它的出身是企业内部的多维报表分析,所以从一开始就重视 SQL 完整性、JOIN 能力、易用性和运维简单——这些正是 ClickHouse 的短板。
Doris 的架构:极简的两种节点
Doris 的架构设计是它”易用”口碑的核心。整个系统只有两种节点,这在分布式系统里算是非常克制了:
┌─────────────────────────────────────────┐
│ FE (Frontend) —— 大脑 │
│ 负责:接收SQL、解析、生成查询计划、 │
│ 管理元数据、调度 │
├─────────────────────────────────────────┤
│ BE (Backend) —— 干活的 │
│ 负责:存储数据、执行查询计算 │
└─────────────────────────────────────────┘
- FE(Frontend):用 Java 写的”大脑”。负责接收你的 SQL、解析、用优化器生成执行计划、管理元数据(表结构等)。FE 可以多个,互相选主保证高可用。
- BE(Backend):用 C++ 写的”肌肉”。真正存数据、做计算的地方。BE 越多,存储和算力越强(水平扩展)。
对比 Hadoop 生态:传统的大数据分析栈要装 HDFS + Hive + ZooKeeper + 一堆组件,运维是噩梦。Doris 把这一切收敛成 FE + BE 两种进程,部署和扩展都极简单。这种”少即是美”的架构,是 Doris 能快速普及的重要原因。
Doris 的三种数据模型
Doris 让你在建表时选择数据模型,这直接对应了第一部分讲的”预聚合 vs 现场计算”思想。这是国产 MPP 数据库一个很有特色的设计,你必须理解:
1. 明细模型(Duplicate Key)——存原始明细,不做任何聚合。
- 对应”现场计算”路线。所有原始行都保留。
- 适合:日志、行为明细,需要查到每一条原始记录的场景。
2. 聚合模型(Aggregate Key)——写入时就按 key 自动聚合。
- 对应”预聚合”路线。比如建表时声明”按 (日期,城市) 聚合,金额求 SUM”,那么写入相同 (日期,城市) 的数据时,金额会自动累加,数据库里永远只存聚合后的结果。
- 适合:固定维度的报表,数据量能大幅压缩,查询飞快。
- 代价:丢失明细,灵活性下降。
3. 主键模型(Unique Key)——同一个主键,新数据覆盖旧数据。
- 解决”更新”问题。相同主键的行,后写的覆盖先写的,实现行级更新。
- 适合:需要实时更新的场景,比如同步 MySQL 的数据变更(CDC)。
为什么这个设计很重要? 它把”用空间换时间”的决策权交给了你。你最懂你的业务查询模式:固定报表就用聚合模型预聚合到极致;需要灵活探查就用明细模型;需要更新就用主键模型。一个数据库,三种存储哲学,按需选用。
Doris 走过的弯路与它和 StarRocks 的渊源
这段历史能帮你彻底理解 Doris 和 StarRocks 的关系,以及它们的技术差异从何而来:
故事是这样的:StarRocks 的核心团队,原本就是百度 Doris(Palo)的核心研发。2020 年,这批人离开后基于 Doris 代码创立了 StarRocks(公司叫鼎石),做了一次激进的重写和性能优化——尤其是把查询执行引擎做了彻底的全面向量化,并重写了 CBO 优化器。
而当时的开源 Doris,向量化引擎还不完善(早期是非向量化的火山模型),性能上一度落后于 StarRocks。
后来 Doris 社区奋起直追:在百度和社区的投入下,Doris 也完成了向量化引擎的重写(Doris 1.x → 2.x),性能大幅提升,并发展出自己的特色(如更强的半结构化数据支持、更广的生态集成)。
所以你现在看到的格局是:StarRocks 和 Doris 像一对”师出同门、各自精进”的兄弟。架构思想高度相似(都是 FE+BE、都有三种数据模型、都是 MPP 向量化),但在具体实现、优化器成熟度、生态侧重、社区治理上各有差异。选型时它俩几乎必然被放在一起 PK。
StarRocks: 把”极速 + 全能”作为信仰的性能怪兽
StarRocks 凭什么”更快”?
三个杀手锏
全面彻底的向量化
StarRocks 从第一天起就是全向量化的 C++ 引擎,从数据扫描、过滤、聚合到 JOIN,每一个算子都向量化。前面讲过,这能榨干 CPU 的 SIMD 能力。这是它和早期 Doris 拉开差距的根本原因之一。
成熟的 CBO 优化器(这是它 JOIN 强的核心)
回忆第一部分讲的 Shuffle 是 MPP 的性能命门。StarRocks 有一个基于代价的优化器(CBO,Cost-Based Optimizer),它能:
- 在多表 JOIN 时,自动判断用哪种 JOIN 方式、按什么顺序 JOIN、要不要 Shuffle,从一堆可能的执行计划里挑出代价最小的那个。
- 这就是为什么 StarRocks 敢说自己”多表 JOIN 性能远超 ClickHouse”——ClickHouse 几乎没有像样的分布式 JOIN 优化器,而 StarRocks 把这件事当成核心能力来做。
这里体现了一个重要的认知:单表查询比的是”引擎执行有多快”(向量化),而多表复杂查询比的是”优化器有多聪明”(CBO)。StarRocks 两手都硬,所以它能同时应对简单和复杂场景。
Colocate Join
消灭 Shuffle 的绝招:
既然 Shuffle 走网络最慢,StarRocks 提供 Colocate Join:如果两张经常 JOIN 的表,建表时就按相同的 key 分片、并强制把相同 key 的数据放在同一台机器上,那么 JOIN 时两边要连接的数据本来就在同一台机器,根本不需要 Shuffle!网络开销直接归零。
这是”用建表时的精心设计,换查询时的极致性能”的典型思路。你会发现 StarRocks 的很多高级特性,本质都是在和 Shuffle 作斗争。
StarRocks 的存算分离与湖仓一体
StarRocks 后来的发展方向,体现了更大的野心,这也是它和 Doris 拉开差异化的地方:
存算分离(Storage-Compute Separation)
传统 MPP(包括早期 StarRocks/Doris)是存算一体:数据存在 BE 节点的本地磁盘上,算力和存储绑死。问题是:想加算力就得连数据一起搬,扩缩容笨重,成本高。
存算分离把数据存到**对象存储(如 S3/对象存储)**上,计算节点变成无状态的、可随时增减的”算力”。需要算的时候拉数据 + 本地缓存加速。
好处:算力可以秒级弹性伸缩(高峰加机器、低谷撤掉),存储成本大降(对象存储便宜)。这是云原生时代的主流方向。
湖仓一体(Lakehouse):直接查数据湖
这就回到了你最开始问的 StarRocks + Iceberg。StarRocks 通过 External Catalog(外部目录),可以不搬运数据就直接查询 Iceberg、Hudi、Hive 等数据湖里的表,并用它的向量化引擎和数据缓存(Data Cache)把这种”湖上查询”加速到接近本地数仓的水平。
战略含义:StarRocks 不只想做一个”自己存数据的数据库”,它想成为整个数据湖之上的统一高速查询层。冷数据躺在便宜的 Iceberg 湖里,StarRocks 提供亚秒级的极速分析——这就是”湖仓一体”的终极形态。
StarRocks 的”表”到底是什么: 一张表的四层切分
前面讲的列存、向量化、MPP、Shuffle 都是”思想”。这些思想最终要落到一个你天天打交道的东西上——表。理解了 StarRocks 的表是怎么组织的,前面所有抽象概念就都”具象”了。
它表面是张普通 SQL 表
StarRocks 兼容 MySQL 协议,建表语法和 MySQL 几乎一样:
CREATE TABLE orders (
order_id BIGINT,
user_id BIGINT,
order_date DATE,
amount DECIMAL(10,2),
city VARCHAR(50)
)
DUPLICATE KEY(order_id) -- ① 数据模型 + 排序键
PARTITION BY RANGE(order_date) (...) -- ② 分区
DISTRIBUTED BY HASH(user_id) BUCKETS 10 -- ③ 分桶
PROPERTIES("replication_num" = "3"); -- ④ 副本数
请特别注意后面这几行——PARTITION BY、DISTRIBUTED BY、replication_num。这些在 MySQL 里是没有的,正是它们把一张”逻辑表”切成了分布式存储。这就是 StarRocks 的表和普通数据库表最本质的区别。
一张表被自上而下切成四层
① 逻辑表 Table —— 你看到的”视图”
这是你 SELECT * FROM orders 面对的行列结构,但它只是个逻辑概念,真实数据被切碎分散了。这一层你要决定两件大事:
- 数据模型(前面 3.3 讲过的明细 / 聚合 / 主键模型)。
- 排序键(Sort Key):数据在每个数据块内按这些列预先排好序存储。查询带这些列做过滤时能大量跳过数据块——这正是”列存 + 预排序”在表层面的落地。
② 分区 Partition —— 按时间/范围切大块
把表按某一列(通常是日期)水平切成若干大块,比如按天,每天数据进各自的分区。两个核心收益:
- 分区裁剪(Partition Pruning):查”6月16日的订单”时,系统只扫那一个分区,其余分区碰都不碰——减少扫描量最有效的手段之一。
- 按分区管理生命周期:删过期数据 = 直接删整个旧分区(秒级、无碎片),冷数据可整分区降冷。这也是为什么”分区数不宜过多”,因为每个分区都有管理成本。
③ 分桶 Bucket / Tablet —— 按哈希切小块(最核心)
在每个分区内部,再按”分桶键”做哈希,拆成 N 个 Tablet。Tablet 是 StarRocks 里最重要的物理单元——数据分布、负载均衡、副本、调度全以它为单位。这里直接呼应前面两个关键思想:
- 决定 Shuffle:若两张表都按
user_id分桶,相同 user_id 的数据天然落在对应 Tablet → 可做 Colocate Join,省掉网络 Shuffle(回忆 4.2 的杀手锏三)。 - 决定并行度:10 个 Tablet 就能让 10 个核/节点并行扫描,这正是 MPP “分而治之”的落地点。
一句话:分区解决”少扫数据”,分桶解决”并行扫 + 少 Shuffle”。
④ 多副本 Replica —— 每个 Tablet 存多份,分散到 BE 节点
每个 Tablet 默认存 3 个副本,分散在不同 BE 机器上:
- 高可用:任一机器宕机,其上的 Tablet 在别处还有副本,数据不丢、查询不中断。
- 并行 + 负载均衡:查询时 FE 把任务拆给各 BE,每个 BE 扫本地副本,多机并行——这就是 MPP 在表这一层真正发生的地方。
把四层串起来:一条查询是怎么跑的
查询 "6月16日 华东用户的订单总额":
① 逻辑表 → 只读 amount 列(列存)
② 分区 → 只锁定 p2026-06-16 一个分区(分区裁剪)
③ 分桶 → 该分区的 N 个 Tablet 分发给多个 BE 并行扫
④ 副本 → 每个 BE 扫本地副本,向量化算局部和,最后汇总
看懂这一层,你就把前面所有”思想”接上了地:列存 = 逻辑表的列;少扫 = 分区裁剪;并行与 Shuffle 优化 = 分桶 Tablet;高可用与 MPP = 多副本分散。StarRocks 的表表面是张普通 SQL 表,内核是一套为分布式极速分析精心设计的数据布局。
补充:Doris 的表组织方式与此高度一致(同样是 表→分区→分桶(Tablet)→副本 四层,同源所致);而 ClickHouse 没有这套”分桶 Tablet + 多副本自动调度”的统一抽象,它的分片和副本更多要靠手工配置分布式表——这也是前面说它”分布式较弱、运维偏复杂”的具体体现。
三者横向对比与选型决策
终极对比表
| 维度 | ClickHouse | Apache Doris | StarRocks |
|---|---|---|---|
| 出身 | Yandex 网站流量分析 | 百度报表系统(Palo) | 从 Doris 团队分叉,激进重写 |
| 核心性格 | 单机/单表速度之王 | 均衡、易用、水桶型 | 极速 + 全能、性能怪兽 |
| 存储引擎 | MergeTree(LSM 思想) | 列存 + 三种数据模型 | 列存 + 三种数据模型 |
| 单表查询 | ⭐⭐⭐⭐⭐ 极强 | ⭐⭐⭐⭐ 强 | ⭐⭐⭐⭐⭐ 极强 |
| 多表 JOIN | ⭐⭐ 弱(优化器薄弱) | ⭐⭐⭐⭐ 强 | ⭐⭐⭐⭐⭐ 极强(CBO + Colocate) |
| 实时更新 | ⭐⭐ 弱(最终一致) | ⭐⭐⭐⭐ 好(主键模型) | ⭐⭐⭐⭐ 好(主键模型) |
| 高并发 | ⭐⭐ 一般 | ⭐⭐⭐⭐ 好 | ⭐⭐⭐⭐⭐ 强 |
| 运维易用性 | ⭐⭐ 较复杂 | ⭐⭐⭐⭐⭐ 极简 | ⭐⭐⭐⭐ 简单 |
| 湖仓一体 | ⭐⭐ 较弱 | ⭐⭐⭐ 支持 | ⭐⭐⭐⭐⭐ 核心卖点 |
| SQL 兼容性 | ⭐⭐⭐ 方言较多 | ⭐⭐⭐⭐ 标准(兼容 MySQL 协议) | ⭐⭐⭐⭐ 标准(兼容 MySQL 协议) |
| 生态/语言 | C++ | C++(BE)+ Java(FE) | C++(BE)+ Java(FE) |
怎么选?给你一套决策思路
不要问”哪个最好”,要问”我的场景需要什么”:
选 ClickHouse,如果:
- 你的核心场景是单张大宽表的日志/事件/指标分析(如埋点分析、监控、APM)。
- 写入量巨大,几乎不更新、不做复杂多表 JOIN。
- 团队能 hold 住它的运维,追求单机极致性价比。
选 Doris,如果:
- 你要一个功能完整、上手快、运维省心的实时数仓。
- 业务有较多多表 JOIN、需要实时更新,又不想折腾复杂运维。
- 看重活跃的开源社区(Apache 顶级项目)和稳妥的演进。
选 StarRocks,如果:
- 你需要复杂多表 JOIN 的极致性能(如灵活多维分析、自助 BI)。
- 你要做湖仓一体,直接高速查询 Iceberg/Hudi/Hive 数据湖。
- 你面临高并发分析场景,追求性能上限。
最后,把整份文档浓缩成一张”思想地图”
所有 OLAP 数据库的快,都建立在四块地基上:
① 列存 → 解决"读得少、压得狠"(I/O 瓶颈)
② 向量化 → 解决"算得快"(CPU 瓶颈)
③ MPP → 解决"装得下、并行算"(容量瓶颈),命门是 Shuffle
④ 预聚合 → 用空间换时间的取舍
在这四块地基上:
• ClickHouse 把 ①②③(单机部分) 做到偏执极致,但放弃了 ③ 的分布式 JOIN 和实时更新
→ 单表之王,专精日志分析
• Doris 把四块地基都做得均衡好用,架构极简
→ 水桶型,易用的全能数仓
• StarRocks 把 ①②③④ 全部推到极致,尤其死磕 ③ 的 Shuffle 优化(CBO+Colocate)
并向"存算分离 + 湖仓一体"进化
→ 性能怪兽,复杂分析与湖仓一体的上限担当