OLAP 数据库全面解析

发布于 2026-06-16

目录

前置理解

在认识任何一个具体产品之前,我们要先回答一个问题:OLAP 数据库到底要解决什么问题?为什么不能用 MySQL?

理解了这个,后面的一切设计选择你都能自己推导出来。

OLTP vs OLAP:两种完全相反的世界

我们平时用的 MySQL、PostgreSQL 属于 OLTP(联机事务处理)。而 StarRocks 这类属于 OLAP(联机分析处理)。这两个词翻译过来都很拗口,但背后的区别非常直观:

OLTP 的典型场景:你在淘宝下了一个订单。

OLAP 的典型场景:老板问”过去一年,华东地区、客单价 500 以上的女性用户,每个月的复购率是多少?”

这里就是一切的起点:OLTP 是”对少数行的频繁读写”,OLAP 是”对海量行的少数列的扫描聚合”。

一个为前者优化的系统,必然不适合后者。这不是”做得好不好”的问题,而是底层数据怎么摆放就决定了它擅长什么。

行存 vs 列存

这是整份文档最重要的一个概念。理解了它,你就理解了 OLAP 数据库一半的设计。

数据在磁盘上是怎么”摆”的?

假设有一张用户表:

idnameagecity
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列: [北京, 上海, 北京]

为什么这个”摆法”决定了一切?

现在回到老板的问题:“统计每个城市的平均年龄。” 这个查询只需要 cityage 两列。

行存怎么做? 磁盘是按行连续存的,所以为了拿到 cityage,它必须把每一行的所有列(包括用不到的 id、name)全部读进内存,然后再从每行里挑出那两列。如果表有 50 列,你为了 2 列的查询,白白读了 48 列的数据。磁盘 I/O 被严重浪费。

列存怎么做? city 列和 age 列本身就是连续存放的。它只读这两列对应的那两块连续磁盘区域,其他 48 列的数据碰都不碰。I/O 量直接降到 1/25。

这就是 OLAP 用列存的根本原因:分析查询的特点是”列少、行多、要聚合”,而列存让你只为你真正用到的列付出 I/O 代价

列存还顺手带来一个巨大的红利:压缩

把同一列的数据放在一起,意味着这块数据类型相同、取值相似。比如 city 列全是城市名,可能就那几十个值在重复;age 列全是 0-120 的小整数。

数据越相似,压缩率越高。常见手段:

行存里一行混着字符串、整数、日期,类型五花八门,压缩器很难下手。列存天然给压缩创造了完美条件。压缩率高 → 磁盘占用小 → 读取的字节更少 → 查询更快,形成正向循环。

记住这个推论链:列存 → 只读需要的列 + 高压缩 → 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 的极致速度来自这几点叠加:

  1. 列存 + 极致压缩:它的压缩算法和编码做得非常激进,I/O 极省。
  2. 向量化 + 手写 SIMD:C++ 写的引擎,针对不同 CPU 指令集(SSE/AVX)手工优化关键算子。
  3. MergeTree 的数据预排序:数据按主键排好序存储,过滤和范围扫描时能大量跳过数据块。
  4. 能榨干单机的每一分性能: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) —— 干活的                   │
│   负责:存储数据、执行查询计算               │
└─────────────────────────────────────────┘

对比 Hadoop 生态:传统的大数据分析栈要装 HDFS + Hive + ZooKeeper + 一堆组件,运维是噩梦。Doris 把这一切收敛成 FE + BE 两种进程,部署和扩展都极简单。这种”少即是美”的架构,是 Doris 能快速普及的重要原因。

Doris 的三种数据模型

Doris 让你在建表时选择数据模型,这直接对应了第一部分讲的”预聚合 vs 现场计算”思想。这是国产 MPP 数据库一个很有特色的设计,你必须理解:

1. 明细模型(Duplicate Key)——存原始明细,不做任何聚合。

2. 聚合模型(Aggregate Key)——写入时就按 key 自动聚合。

3. 主键模型(Unique Key)——同一个主键,新数据覆盖旧数据。

为什么这个设计很重要? 它把”用空间换时间”的决策权交给了你。你最懂你的业务查询模式:固定报表就用聚合模型预聚合到极致;需要灵活探查就用明细模型;需要更新就用主键模型。一个数据库,三种存储哲学,按需选用。

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),它能:

这里体现了一个重要的认知:单表查询比的是”引擎执行有多快”(向量化),而多表复杂查询比的是”优化器有多聪明”(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 BYDISTRIBUTED BYreplication_num这些在 MySQL 里是没有的,正是它们把一张”逻辑表”切成了分布式存储。这就是 StarRocks 的表和普通数据库表最本质的区别。

一张表被自上而下切成四层

① 逻辑表 Table —— 你看到的”视图”
这是你 SELECT * FROM orders 面对的行列结构,但它只是个逻辑概念,真实数据被切碎分散了。这一层你要决定两件大事:

② 分区 Partition —— 按时间/范围切大块
把表按某一列(通常是日期)水平切成若干大块,比如按天,每天数据进各自的分区。两个核心收益:

③ 分桶 Bucket / Tablet —— 按哈希切小块(最核心)
在每个分区内部,再按”分桶键”做哈希,拆成 N 个 Tablet。Tablet 是 StarRocks 里最重要的物理单元——数据分布、负载均衡、副本、调度全以它为单位。这里直接呼应前面两个关键思想:

一句话:分区解决”少扫数据”,分桶解决”并行扫 + 少 Shuffle”。

④ 多副本 Replica —— 每个 Tablet 存多份,分散到 BE 节点
每个 Tablet 默认存 3 个副本,分散在不同 BE 机器上:

把四层串起来:一条查询是怎么跑的

查询 "6月16日 华东用户的订单总额":
  ① 逻辑表 → 只读 amount 列(列存)
  ② 分区   → 只锁定 p2026-06-16 一个分区(分区裁剪)
  ③ 分桶   → 该分区的 N 个 Tablet 分发给多个 BE 并行扫
  ④ 副本   → 每个 BE 扫本地副本,向量化算局部和,最后汇总

看懂这一层,你就把前面所有”思想”接上了地:列存 = 逻辑表的列;少扫 = 分区裁剪;并行与 Shuffle 优化 = 分桶 Tablet;高可用与 MPP = 多副本分散。StarRocks 的表表面是张普通 SQL 表,内核是一套为分布式极速分析精心设计的数据布局。

补充:Doris 的表组织方式与此高度一致(同样是 表→分区→分桶(Tablet)→副本 四层,同源所致);而 ClickHouse 没有这套”分桶 Tablet + 多副本自动调度”的统一抽象,它的分片和副本更多要靠手工配置分布式表——这也是前面说它”分布式较弱、运维偏复杂”的具体体现。

三者横向对比与选型决策

终极对比表

维度ClickHouseApache DorisStarRocks
出身Yandex 网站流量分析百度报表系统(Palo)从 Doris 团队分叉,激进重写
核心性格单机/单表速度之王均衡、易用、水桶型极速 + 全能、性能怪兽
存储引擎MergeTree(LSM 思想)列存 + 三种数据模型列存 + 三种数据模型
单表查询⭐⭐⭐⭐⭐ 极强⭐⭐⭐⭐ 强⭐⭐⭐⭐⭐ 极强
多表 JOIN⭐⭐ 弱(优化器薄弱)⭐⭐⭐⭐ 强⭐⭐⭐⭐⭐ 极强(CBO + Colocate)
实时更新⭐⭐ 弱(最终一致)⭐⭐⭐⭐ 好(主键模型)⭐⭐⭐⭐ 好(主键模型)
高并发⭐⭐ 一般⭐⭐⭐⭐ 好⭐⭐⭐⭐⭐ 强
运维易用性⭐⭐ 较复杂⭐⭐⭐⭐⭐ 极简⭐⭐⭐⭐ 简单
湖仓一体⭐⭐ 较弱⭐⭐⭐ 支持⭐⭐⭐⭐⭐ 核心卖点
SQL 兼容性⭐⭐⭐ 方言较多⭐⭐⭐⭐ 标准(兼容 MySQL 协议)⭐⭐⭐⭐ 标准(兼容 MySQL 协议)
生态/语言C++C++(BE)+ Java(FE)C++(BE)+ Java(FE)

怎么选?给你一套决策思路

不要问”哪个最好”,要问”我的场景需要什么”:

选 ClickHouse,如果:

选 Doris,如果:

选 StarRocks,如果:

最后,把整份文档浓缩成一张”思想地图”

所有 OLAP 数据库的快,都建立在四块地基上:
  ① 列存      → 解决"读得少、压得狠"(I/O 瓶颈)
  ② 向量化    → 解决"算得快"(CPU 瓶颈)
  ③ MPP       → 解决"装得下、并行算"(容量瓶颈),命门是 Shuffle
  ④ 预聚合    → 用空间换时间的取舍

在这四块地基上:
  • ClickHouse 把 ①②③(单机部分) 做到偏执极致,但放弃了 ③ 的分布式 JOIN 和实时更新
        → 单表之王,专精日志分析
  • Doris 把四块地基都做得均衡好用,架构极简
        → 水桶型,易用的全能数仓
  • StarRocks 把 ①②③④ 全部推到极致,尤其死磕 ③ 的 Shuffle 优化(CBO+Colocate)
    并向"存算分离 + 湖仓一体"进化
        → 性能怪兽,复杂分析与湖仓一体的上限担当