恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
OLAP从原理到选型:列式存储、MPP与主流引擎实战指南
首页
资讯中心
/
OLAP从原理到选型:列式存储、MPP与主流引擎实战指南
OLAP从原理到选型:列式存储、MPP与主流引擎实战指南
发布时间:2026/9/23 22:07:10
1. 为什么我们需要认真聊聊OLAP数据分析这个行当里OLAP是个绕不开的词。你去看任何一款数据产品的介绍十有八九会提到“支持OLAP分析”“OLAP引擎”“实时OLAP”之类的字眼。但真要让人用一句话说清楚OLAP到底是什么很多人会卡壳。我自己刚入行那会儿也一样面试被问到“说说你对OLAP的理解”脑子里第一反应就是“联机分析处理”这六个字然后呢然后就没有然后了。这篇文章我想把OLAP这件事从头到尾捋一遍。不光是概念定义更重要的是它为什么存在、它和OLTP到底差在哪里、底层是怎么实现的、市面上那些五花八门的OLAP引擎各自适合什么场景、以及在实际项目中怎么选型、怎么避坑。如果你是一个数据工程师、数据分析师、后端开发或者只是单纯想搞明白“为什么查个报表有时候快有时候慢”这篇内容应该都能给你一些实在的参考。我尽量不说废话用从业者之间聊天的口吻来写。有些地方会涉及比较底层的原理我会尽量用生活化的类比来解释有些地方会涉及具体的参数和配置我会把计算过程和选择理由都写清楚。整篇内容会比较长建议你找个安静的时间慢慢看或者收藏起来当参考手册用。2. OLAP到底是什么从一次慢查询说起2.1 一个真实场景引发的思考假设你在一家电商公司做数据开发。某天运营同事跑过来跟你说“我想看一下过去三年每个品类在每个省份的月度销售额趋势顺便按同比环比排个序。”你打开数据库写了一条SQL涉及三张表关联加上GROUP BY、ORDER BY、窗口函数然后点击执行。接下来发生的事情取决于你用的什么数据库如果是一套典型的事务型数据库这条查询可能跑了十几分钟还没出结果甚至直接把数据库拖垮影响到线上交易。这个场景就是OLAP要解决的核心问题。OLAP的全称是Online Analytical Processing中文叫联机分析处理。它和OLTP联机事务处理是两种截然不同的数据处理模式。OLTP关心的是“把一笔交易准确地记下来”比如用户下单、支付、修改地址OLAP关心的是“从海量历史数据里挖出规律和趋势”比如上面那个运营同事的需求。你可以这样理解OLTP像是超市的收银台每一笔交易都要快速、准确地完成不能出错OLAP像是超市总部的分析师拿着过去几年的销售小票试图找出“哪个品类的纸巾在哪个季节卖得最好”。两者的目标不同所以底层的数据组织方式、存储结构、查询优化策略也完全不同。2.2 OLAP和OLTP的核心差异很多人会把这两个概念混淆或者只是模糊地知道“一个管交易一个管分析”。我把它们的关键差异整理成一张表方便你对照理解。对比维度OLTP联机事务处理OLAP联机分析处理核心目标快速、准确地处理单条或少量记录从大量数据中聚合、分析、挖掘规律典型操作INSERT、UPDATE、DELETE、点查SELECT GROUP BY、JOIN、窗口函数数据量级单次操作涉及少量行单次查询扫描百万到百亿行响应时间毫秒级秒级到分钟级视引擎而定数据时效当前最新状态历史快照、时间序列并发量高并发、短连接低并发、长查询存储方式行式存储为主列式存储为主索引策略B树、哈希索引分区、排序键、位图索引、Zone Map典型产品MySQL、PostgreSQL、OracleClickHouse、Doris、StarRocks、Presto这张表里的每一行都值得展开说但我先挑最核心的一点行式存储和列式存储的区别。这是OLAP性能优势的根基。2.3 行存和列存为什么OLAP快得起来假设有一张用户订单表包含订单ID、用户ID、商品名称、品类、金额、下单时间、省份这几个字段。如果按行存储每一行的数据在磁盘上是连续存放的就像Excel表格一样一行一行往下排。这种存储方式对OLTP非常友好因为你要查“订单ID为12345的详情”只需要定位到那一行把整行数据读出来就行。但OLAP的查询往往是这样的“统计每个省份的总销售额”。这个查询只需要用到“省份”和“金额”两列其他字段完全不需要。如果是行式存储数据库不得不把每一行的所有字段都读出来然后再丢弃不需要的列。这就好比你要从一本书里找所有提到“北京”的句子但每次都得把整页纸复印一遍才能看。列式存储则完全不同。它把每一列的数据单独存放在一起省份列的所有值连续排列金额列的所有值连续排列。当查询只需要省份和金额时数据库只读取这两列的数据其他列碰都不碰。I/O量可能只有行式存储的十分之一甚至更少。而且同一列的数据类型相同压缩效率极高进一步减少了磁盘读取量。注意列式存储并不是银弹。如果你的查询模式是“查某一条记录的完整信息”列存的性能反而不如行存因为需要把分散在各列的数据重新拼成一行。所以OLAP引擎通常不擅长点查这是设计上的取舍。3. OLAP的技术演进从MOLAP到现代湖仓3.1 三代OLAP技术的核心思路OLAP这个概念从上世纪九十年代被正式提出到现在经历了几个明显的技术阶段。每个阶段都在解决前一代的痛点同时也在新的场景下暴露出新的问题。第一代MOLAP多维OLAP。核心思路是预计算。系统提前把各种可能的维度组合和聚合结果算好存成一个多维数据立方体Data Cube。查询的时候直接从这个立方体里取数速度极快。但问题也很明显维度一多立方体的体积会爆炸式增长。比如10个维度、每个维度10个值理论上的组合数就是10的10次方根本存不下。而且数据更新后需要重新构建立方体时效性差。第二代ROLAP关系型OLAP。不再预计算所有组合而是把数据存在关系型数据库里查询时通过SQL动态聚合。灵活性大大提升但性能依赖底层数据库的优化能力。早期很多ROLAP方案就是在MySQL或Oracle上硬扛数据量一大就撑不住了。第三代现代列式OLAP引擎。以ClickHouse、Doris、StarRocks为代表结合了列式存储、向量化执行、MPP架构、智能索引等技术在灵活性和性能之间找到了更好的平衡。这也是目前大多数互联网公司的首选方案。3.2 现代OLAP引擎的四大核心技术要理解现代OLAP为什么能做到“亿级数据秒级响应”需要搞明白四个关键技术。我用做菜来打个比方列式存储是食材预处理向量化执行是批量烹饪MPP是多个厨师同时开工智能索引是提前备好的调料包。列式存储前面已经讲过了核心价值在于减少I/O和提升压缩率。这里补充一个数据在实际业务场景中列式存储的压缩比通常能达到5:1到20:1意味着原本需要1TB存储的数据压缩后可能只需要50GB到200GB。这不仅省磁盘更重要的是减少了查询时需要读取的数据量。向量化执行是指CPU一次处理一批数据而不是一行一行地处理。传统的火山模型Volcano Model每次只处理一行函数调用开销大CPU缓存命中率低。向量化执行把数据按批比如1024行加载到CPU缓存中用SIMD指令并行计算性能可以提升几倍到几十倍。你可以理解为以前是一个一个搬砖现在是用传送带一次搬一堆。MPP架构Massively Parallel Processing是指把一个大查询拆成多个子任务分发到多台机器上并行执行最后汇总结果。比如要统计10亿行数据的销售额单机可能需要几十秒但如果分成100个分片每台机器只处理1000万行理论上1秒就能完成。当然实际会有网络传输和结果合并的开销但整体加速比仍然非常可观。智能索引包括Zone Map、Bitmap索引、Bloom Filter等。Zone Map记录每个数据块中某列的最小值和最大值查询时如果条件不在这个范围内直接跳过整个数据块。Bitmap索引适合低基数列比如性别、省份可以快速做交并集运算。Bloom Filter用于快速判断某个值是否存在于某个数据块中避免不必要的读取。3.3 从数据仓库到湖仓一体OLAP引擎的演进还伴随着数据架构的变迁。早期大家用数据仓库Data Warehouse数据经过ETL清洗后加载到仓库里再在仓库上做OLAP分析。后来数据湖Data Lake兴起原始数据直接存到HDFS或对象存储上灵活但查询性能差。现在的趋势是湖仓一体Lakehouse在数据湖上直接构建OLAP能力兼顾灵活性和性能。这个演进对OLAP引擎提出了新的要求不仅要查得快还要能直接访问湖上的开放格式如Parquet、ORC、Iceberg支持Schema演进支持事务一致性。Doris和StarRocks在这方面做得比较靠前ClickHouse也在通过外部表的方式逐步补齐。4. 主流OLAP引擎选型没有最好只有最合适4.1 选型前必须想清楚的五个问题每次有人问我“哪个OLAP引擎最好”我都会先反问五个问题。这五个问题的答案基本能决定选型方向。第一数据量有多大是千万级、亿级还是百亿级不同引擎在数据量上的表现差异很大。ClickHouse在单表亿级到百亿级场景下性能极强但JOIN能力相对弱Doris和StarRocks在中等数据量下表现均衡JOIN支持更好。第二查询模式是什么是固定的报表查询还是灵活的自助分析固定报表可以用预计算加速灵活分析则需要引擎有强大的即席查询能力。Presto/Trino在即席查询上很擅长但延迟通常比ClickHouse高。第三数据实时性要求多高是T1就够了还是需要秒级可见ClickHouse和Doris都支持实时写入但Doris的实时更新能力更强适合需要频繁UPSERT的场景。第四团队技术栈是什么如果团队已经重度使用Hadoop生态Presto/Trino或Hive on Spark可能更顺手如果团队偏Java技术栈Doris和StarRocks的运维成本更低。第五运维成本能接受多少ClickHouse的运维相对复杂集群扩缩容、数据重分布需要人工介入较多Doris和StarRocks在运维自动化上做得更好但资源消耗也更高。4.2 主流引擎对比与适用场景我把目前市面上最常用的几款OLAP引擎整理成一张对比表方便你快速定位。引擎核心优势主要短板典型适用场景ClickHouse单表查询极快、压缩率高、成本低JOIN弱、UPDATE/DELETE弱、运维复杂日志分析、用户行为分析、宽表聚合Apache Doris实时更新强、JOIN好、运维简单极致性能略逊于ClickHouse实时报表、数据看板、多维分析StarRocks性能均衡、物化视图强、湖仓能力好社区相对Doris略小湖仓一体、实时分析、高并发查询Presto/Trino联邦查询强、即席查询灵活延迟较高、内存消耗大跨源即席分析、Ad-hoc查询Apache Kylin预计算能力强、查询极快灵活性差、Cube膨胀固定维度组合的报表Druid时序数据强、实时摄入好JOIN弱、SQL支持有限监控指标、时序分析这张表只是一个大致的参考实际选型还要结合具体业务。我见过不少团队一开始选了ClickHouse后来因为JOIN需求越来越多不得不迁移到Doris或StarRocks。也见过团队用Doris做日志分析发现单表聚合性能不如ClickHouse又加了一套ClickHouse专门做日志。没有哪个引擎能通吃所有场景混合架构往往是更务实的选择。4.3 选型时容易踩的三个坑第一个坑是只看Benchmark不看业务。网上有很多TPC-H、TPC-DS的跑分对比但那些测试场景和你的实际业务可能差很远。比如你的查询都是宽表聚合ClickHouse可能碾压其他引擎但如果你的查询涉及多表JOINClickHouse可能直接OOM。选型前一定要用自己的真实数据和查询跑一遍。第二个坑是低估运维成本。有些引擎在测试环境跑得很好上了生产才发现扩缩容、数据均衡、故障恢复都很麻烦。ClickHouse的分布式表需要手动管理分片和副本Doris和StarRocks在这方面自动化程度更高。如果团队没有专职的DBA建议优先考虑运维友好的方案。第三个坑是忽视数据更新需求。很多OLAP引擎擅长批量导入但不擅长频繁更新。如果你的业务需要实时UPSERT比如订单状态变更一定要选支持主键模型的引擎。Doris的Unique Key模型和StarRocks的主键模型在这方面表现较好ClickHouse的ReplacingMergeTree虽然也能做但查询时需要额外处理。5. OLAP实操从建表到查询优化的完整流程5.1 建表分区、分桶与排序键的设计建表是OLAP使用的第一步也是最容易埋坑的一步。以Doris为例建表时需要重点考虑三个设计分区Partition、分桶Bucket和排序键Sort Key。分区通常按时间字段来做比如按天或按月分区。这样做的好处是查询时可以分区裁剪只扫描相关时间段的数据。比如查询“最近7天”的数据如果按天分区引擎只需要扫描7个分区而不是全表。分区的粒度需要根据数据量和查询模式来定数据量大、查询频繁按天过滤就按天分区数据量小、查询按月过滤就按月分区。分桶是把每个分区内的数据进一步切分到不同的桶里每个桶是一个独立的物理文件。分桶键的选择很关键要选高基数的列比如用户ID并且是查询中常用的JOIN键或过滤键。分桶数建议是机器数的整数倍这样数据分布更均匀。如果分桶数太少单个桶太大查询并行度不够如果分桶数太多小文件过多元数据管理开销大。排序键决定了数据在桶内的物理排序顺序。查询时如果过滤条件命中了排序键的前缀可以快速定位到数据块减少扫描量。排序键的设计原则是把最常用的过滤字段放在前面基数高的字段放在后面。比如查询经常按“省份城市”过滤排序键就设为省份城市。-- Doris建表示例 CREATE TABLE sales_analysis ( order_date DATE, province VARCHAR(50), city VARCHAR(50), category VARCHAR(50), sales_amount DECIMAL(18,2), order_count INT ) ENGINEOLAP DUPLICATE KEY(order_date, province, city) PARTITION BY RANGE(order_date) ( PARTITION p202401 VALUES [(2024-01-01), (2024-02-01)), PARTITION p202402 VALUES [(2024-02-01), (2024-03-01)) ) DISTRIBUTED BY HASH(province) BUCKETS 32 PROPERTIES ( replication_num 3, storage_medium SSD );提示分桶数不是越多越好。一般建议单个桶的数据量在1GB到10GB之间。如果单个桶太小比如只有几十MB元数据和调度开销会占比过高如果单个桶太大比如超过50GB查询并行度不够性能会下降。5.2 数据导入批量与实时的取舍OLAP引擎的数据导入方式主要分两种批量导入和实时导入。批量导入适合T1的离线场景通常从Hive、Spark或对象存储中一次性加载大量数据。实时导入适合需要秒级可见的场景通常从消息队列如Kafka中持续消费数据。以Doris为例批量导入常用Broker Load或Spark Load实时导入常用Routine Load或Flink Connector。Broker Load适合从HDFS或S3导入大文件吞吐量高Routine Load适合从Kafka持续消费延迟低但吞吐量受限于消息队列的分区数。导入过程中最容易遇到的问题是两个数据倾斜和版本冲突。数据倾斜是指某些分桶的数据量远大于其他分桶导致部分节点成为瓶颈。解决方法是在导入前对分桶键做预处理或者在导入时设置更高的并行度。版本冲突是指并发导入时同一批次的数据被多次写入导致查询结果重复。解决方法是在导入时指定唯一的Label引擎会自动去重。5.3 查询优化从执行计划到物化视图查询优化是OLAP使用中最考验功力的环节。同样一条SQL写法不同性能可能差几十倍。我总结了几条最实用的优化原则。第一尽量用分区裁剪。查询条件里一定要带上分区字段否则引擎会扫描全表。比如查询“2024年1月的销售额”WHERE条件里必须写order_date 2024-01-01 AND order_date 2024-02-01而不是只写month 1。**第二避免SELECT ***。列式存储的优势是按需读取如果写了SELECT *引擎需要读取所有列I/O量大幅增加。只选需要的列性能提升立竿见影。第三JOIN时把小表放在右边。大多数OLAP引擎的JOIN实现是“右表广播”把小表广播到所有节点大表留在本地扫描。如果写反了大表被广播网络传输会成为瓶颈。第四善用物化视图。物化视图是预计算的聚合结果查询时如果命中物化视图可以直接返回结果无需扫描原始数据。Doris和StarRocks都支持自动物化视图改写建好物化视图后优化器会自动判断是否可以使用。-- 创建物化视图示例 CREATE MATERIALIZED VIEW mv_sales_by_province AS SELECT province, category, DATE_TRUNC(order_date, month) AS month, SUM(sales_amount) AS total_sales, COUNT(*) AS order_count FROM sales_analysis GROUP BY province, category, DATE_TRUNC(order_date, month);注意物化视图不是越多越好。每个物化视图都会占用存储空间并且在数据导入时需要同步更新。如果物化视图过多导入性能会明显下降。建议只为最核心、最频繁的查询创建物化视图。5.4 资源管理与并发控制OLAP引擎通常支持多租户和资源隔离。以Doris为例可以通过Resource Group把不同的查询分配到不同的资源组限制CPU和内存使用。这样可以避免一个大查询把整个集群的资源耗尽影响其他业务。并发控制方面大多数引擎支持查询队列和并发上限。如果并发查询数超过阈值新的查询会排队等待而不是直接失败。队列的长度和超时时间需要根据业务容忍度来设置。对于延迟敏感的报表查询可以设置较高的优先级对于后台的ETL查询可以设置较低的优先级。6. 常见问题与排查技巧实录6.1 查询变慢的五大原因与排查路径在实际运维中查询变慢是最常见的问题。我整理了一个排查路径按优先级从高到低排列。排查项可能原因排查方法解决方案分区裁剪失效WHERE条件未命中分区字段查看执行计划中的分区扫描范围修改SQL补上分区过滤条件数据倾斜分桶键分布不均查看各节点扫描行数和耗时调整分桶键或增加分桶数小文件过多频繁导入导致碎片查看分区下的文件数量执行Compaction合并小文件内存不足大JOIN或大聚合查看查询内存使用峰值优化SQL或增加内存限制并发过高同时运行的查询太多查看当前运行查询数和队列长度增加资源组或限制并发这个表格里的每一项我都实际遇到过。最隐蔽的是数据倾斜因为查询本身可能不报错只是慢。有一次我们一个查询跑了半小时最后发现是某个省份的数据量是其他省份的100倍导致那个分桶的节点成了瓶颈。后来把分桶键从省份改成用户ID问题就解决了。6.2 数据导入失败的典型场景数据导入失败通常有几种表现任务超时、版本冲突、数据质量问题。任务超时最常见的原因是导入的数据量超过了单次导入的限制或者网络带宽不足。解决方法是拆分导入任务或者调整导入的超时参数。版本冲突通常发生在并发导入同一张表时。Doris的导入任务是按Label去重的如果两个任务用了相同的Label第二个任务会被拒绝。解决方法是确保每个导入任务的Label唯一通常用“表名时间戳随机数”来生成。数据质量问题包括字段类型不匹配、空值约束冲突、分区字段格式错误等。这类问题最好在导入前做数据校验比如用Spark或Flink做一层ETL清洗确保数据符合目标表的Schema。6.3 集群扩缩容的注意事项OLAP集群的扩缩容不是简单的加机器或减机器。以Doris为例扩容时需要把新节点加入集群然后触发数据重分布把部分分片迁移到新节点上。这个过程会占用网络和磁盘I/O建议在业务低峰期执行。缩容更麻烦需要先把要下线节点上的数据迁移到其他节点确认数据完整后再下线。如果直接下线节点可能导致数据丢失或副本数不足。任何扩缩容操作前一定要先备份元数据并且确认副本数大于1。提示扩缩容后建议观察一段时间比如24小时的查询性能和稳定性确认没有异常后再进行下一步操作。我见过扩容后因为数据分布不均导致部分节点负载反而更高的案例。6.4 我的三条避坑心得第一条不要在生产环境直接跑大查询。新写的SQL先在测试环境跑一遍看看执行计划和资源消耗。如果测试环境数据量太小看不出问题可以用EXPLAIN命令分析执行计划重点关注扫描行数和是否命中索引。第二条监控比调优更重要。很多问题在爆发前都有征兆比如查询延迟逐渐上升、磁盘使用率持续增长、导入任务排队变长。建好监控告警把问题扼杀在萌芽阶段比事后救火轻松得多。第三条文档和规范要落地。建表规范、命名规范、查询规范这些看起来是小事但团队大了之后没有规范就会乱套。比如有人用日期做分区有人用字符串做分区查询时根本没法统一优化。建议在项目初期就把规范定好并且用代码审查来保证执行。7. 写在最后的一点个人体会OLAP这个领域变化很快新引擎、新架构、新优化技术层出不穷。但底层的东西其实没怎么变列式存储、向量化执行、MPP、智能索引这些核心原理从十年前到现在一直适用。把原理搞明白了再去看那些新出的引擎你会发现它们只是在某个维度上做了改进而不是颠覆。我在实际项目中的体会是选型时不要追求“最先进”或“性能最强”而要选“最适合当前团队和业务”的。一个运维复杂但性能极致的引擎如果团队没有能力驾驭反而会成为负担。相反一个性能中上但稳定易用的引擎可能带来更大的整体价值。另外OLAP不是孤立的。它和上游的数据采集、ETL、消息队列下游的BI、报表、数据应用是一个完整的链路。只优化OLAP引擎本身往往达不到最好的效果。比如上游数据质量差OLAP里再怎么优化也查不出正确结果下游BI工具写法不当再快的引擎也扛不住。所以做OLAP优化要有全局视角。最后分享一个小技巧如果你不确定某个查询为什么慢先用EXPLAIN看看执行计划重点关注三个指标——扫描行数、扫描字节数、是否命中分区裁剪。这三个指标基本能定位80%的性能问题。剩下的20%再去看数据分布、资源竞争和并发情况。这个排查顺序我用了很多年屡试不爽。