我做过一次很丢人的压测。用ClickHouse跑TPC-H 1TB基准,一个简单的group by查询,3秒出头。同一个查询扔给MySQL,跑了2分半,我当时就懵了。一开始我不信,以为是MySQL没调优。但调优之后还是差了一个数量级。后来我明白了,不是MySQL不行,是行存储和列存储的物理鸿沟。
底层拆解:列式存储到底改了什么

列式存储的核心很简单:把一整行数据拆开,按列存。但带来的连锁反应极其深远。拿压缩来说,同一列的数据类型一致,数值分布也更聚集。字典编码、RLE编码能让数据量缩到原来的十分之一。我给你打个比方:你去图书馆找书,行存储就是给每本书编个号,然后整柜子乱放,找一本还要翻半天。列式存储呢?同样的作者,同样的主题,全都码在同一个架子上,扫一眼就找到了。这个类比比较粗糙——但物理IO的差距就是这么直观。
关键点是:存储格式决定了查询性能的上限。索引再牛,也得先过IO这道坎。
压缩的数学原理也不复杂。RLE适合那种连续重复的列,比如地区编码。字典编码适合基数低的列。TPC-H测试中,用上压缩之后,1TB的数据实际可能只占300GB,查询扫描的IO量直接减少70%。
向量化执行与延迟物化:别再把数据捞回来了
这里讲查询执行引擎。向量化执行是让CPU一次处理一批数据,而不是一行行处理。原因在于现代CPU的SIMD指令集。我们做压测的时候,开启向量化执行之后,聚合计算的速度提升了差不多20倍。因为每条指令处理的字节数变多了,缓存命中率也上去了。
延迟物化更微妙——别急着把数据捞回来。传统的行存储查询,过滤条件发生在行上,但列存储如果你在早期就把所有列都拼接回来,那就失去了列存储的优势。所以要把过滤和投影操作尽可能下沉,直到最后才构建结果行。这个优化在星型模型关联大表的时候,能省下不少归并排序的开销。

数据说话:一次真实的迁移案例

我去年把公司一个核心报表系统从Hive迁移到了Iceberg+StarRocks的组合。跑了两个月,数据量大概25TB,查询一个月前的订单聚合。原先Hive跑一个SQL要15分钟左右,现在3秒到5秒。压测结果:q1扫25亿行,耗时4.2秒;q2带多表关联,6.8秒。算下来加速比差不多200倍。
注意这些数据是我们自己压的,当然环境有差异,但趋势很明确。列式存储+向量化+物化视图的组合拳,能对付绝大多数分析场景。
落地必踩的三个坑
坑1:数据模型设计的过度规范化。很多从行数据库转过来的团队,习惯把维度表拆得极细,结果query里一堆join,把列存储的性能优势全吞了。解决方案:尽量用宽表,星型模型优先。我们当时把5张关联表合并成一张宽表,查询时间降了40%。
坑2:小文件问题。分区键选错了,比如按小时分区,结果每小时只有几十MB,会生成大量小文件,扫描的时候节点得处理上百个文件句柄,IO模型直接崩。解决方案:分区粒度按天,或者用分桶+物化视图提前压缩文件。我们上次就因为分区太多,某次查询卡了20分钟,排查出来是5000多个小文件在排队。
坑3:数据倾斜。在分布式数据仓库里,最怕某些键的值过多。比如按用户ID分组,头部用户占了99%的数据量,同一个节点直接算到OOM。我们在压测时发现,一个聚合查询最后落到一个节点上,需要16GB内存,结果那个节点只有8GB。解决方案:加个盐值,或者用两阶段聚合。这个方案我们用了,直接把最差情况的执行时间从30分钟压到5分钟。

说白了,数据仓库不是数据库加个报表工具,它整个存储和计算架构都得围绕分析场景重想。列式存储、向量化、延迟物化这些技术,都是把物理层的每一分力气用在刀刃上。我刚用数据仓库的时候也踩了不少坑,但理解底层之后,很多问题一眼就能看穿。这套东西,值得每个做数据的人学一遍。