分析型场景下的SQL查询大多涉及全表扫描、多列聚合、范围过滤等操作,这类操作不需要读取整行数据,只需要处理目标列的内容。列式执行引擎正是针对这类需求设计,将同一列的数据连续存储,执行查询时仅加载需要的列,从存储和执行两个层面优化性能。

分析型场景的核心需求特征
分析型场景和事务型场景的需求差异明显,核心特征包括:
- 查询多为聚合类操作,比如求和、计数、平均值计算,很少需要读取完整行数据
- 数据量通常达到TB甚至PB级别,全表扫描是常见操作
- 查询涉及大量数据的批量处理,对CPU和内存的利用效率要求高
- 数据写入后很少修改,以批量追加为主,适合压缩存储
列式执行引擎的核心优势
1. 更高的数据压缩效率
同一列的数据通常具有相同的数据类型和相似的取值范围,比如用户年龄列都是整数,订单金额列都是小数,这类数据的重复度很高。列式存储可以将同一列的数据连续存放,使用字典编码、游程编码、差值编码等压缩算法时,压缩比远高于行式存储。
比如用户性别列只有男、女两个取值,使用字典编码只需要用0和1表示,压缩后占用的存储空间可能只有原来的十分之一。更少的存储空间意味着更少的磁盘IO,查询时加载数据的时间也会大幅缩短。
2. 减少无效数据读取,提升IO效率
行式执行引擎执行查询时,即使只需要其中2列的数据,也需要读取整行的所有列内容,再过滤出需要的列。而列式执行引擎执行查询时,只会加载查询涉及的目标列数据,完全跳过不需要的列。
比如执行SELECT AVG(age) FROM user的查询,行式引擎需要读取user表的所有行所有列,而列式引擎只需要读取age列的数据,IO量可能只有原来的几十分之一,查询速度自然更快。
3. 适配向量化执行,提升CPU利用率
列式数据在内存中是连续存储的,天然适合向量化执行模式。向量化执行可以一次处理一批数据,而不是逐行处理,减少函数调用和分支判断的开销,同时可以利用CPU的SIMD指令集并行处理多个数据,大幅提升CPU的利用效率。
以下是一个简单的向量化求和的伪代码示例,展示列式数据批量处理的逻辑:
-- 模拟列式存储的年龄列数据,假设已经加载到内存数组age_col中
-- 向量化求和逻辑,一次处理1024个数据
DECLARE sum_val INT DEFAULT 0;
DECLARE batch_size INT DEFAULT 1024;
DECLARE i INT DEFAULT 0;
DECLARE col_len INT DEFAULT LENGTH(age_col);
WHILE i < col_len DO
-- 一次处理batch_size个数据,利用SIMD指令加速
DECLARE end_idx INT DEFAULT LEAST(i + batch_size, col_len);
FOR j FROM i TO end_idx - 1 DO
sum_val = sum_val + age_col[j];
END FOR;
i = end_idx;
END WHILE;
SELECT sum_val / col_len AS avg_age;
4. 延迟物化减少内存开销
延迟物化是列式执行引擎的重要优化手段,指的是尽量推迟将列数据拼接成完整行的时间。很多分析型查询只需要对列做聚合操作,不需要拼接行,延迟物化可以让数据一直以列的形式在内存中处理,减少内存占用。
比如查询SELECT region, COUNT(*) FROM order GROUP BY region,列式引擎只需要读取region列做分组聚合,不需要拼接order表的其他列,内存中只需要存放region列的数据和聚合中间结果,内存开销远低于行式引擎。
5. 更适配分析型查询的优化规则
列式执行引擎可以针对分析型查询做更多针对性优化,比如列裁剪、谓词下推时可以更精准地定位需要处理的列,聚合操作时可以直接对列数据做批量计算,不需要逐行提取数据。这些优化在行式引擎中要么无法实现,要么优化效果有限。
列式执行引擎的适用边界
列式执行引擎虽然适配分析型场景,但并不适合事务型场景。事务型场景多为点查询、增删改操作,需要频繁读取整行数据,列式存储反而会增加拼接行的开销,性能不如行式引擎。因此很多混合负载的数据库会同时支持行式和列式两种执行引擎,根据查询类型自动选择。
总结
分析型场景的核心需求是高效处理海量数据的批量聚合查询,列式执行引擎通过按列存储、减少无效IO、支持向量化执行、延迟物化等特性,完美匹配这类需求,相比行式执行引擎有数倍甚至数十倍的性能提升。在选型分析型数据库或者优化分析型查询时,列式执行引擎是核心考量因素之一。