当前位置: 首页 > news >正文

深入理解列式存储与向量化引擎

前言

在现代数据分析(OLAP)领域,性能是至关重要的。为了处理海量数据并实现近乎实时的查询响应,数据库和计算引擎的技术已经从传统的面向事务(OLTP)的设计,演进到了以**列式存储(Columnar Storage)为基础,以向量化执行(Vectorized Execution)**为核心的新范式。

本文档将详细拆解这两项关键技术,帮助你理解它们是什么、为什么需要、以及它们如何协同工作,创造出数量级的性能提升。

一、 列式存储(Columnar Storage)

1.1 什么是列式存储?

列式存储是一种数据在磁盘或内存中按**列(Column)**连续组织和存放的策略。

为了直观理解,我们来看一个简单的用户表示例:

UserID (整型)Name (字符串)Age (整型)
1Alice30
2Bob25
3Charlie35
存储方式对比
  • 行式存储(Row-based / OLTP 常用)
    数据按行连续存放,一行的数据紧挨在一起。

    [1, 'Alice', 30], [2, 'Bob', 25], [3, 'Charlie', 35]
    
  • 列式存储(Column-based / OLAP 常用)
    数据按列连续存放,同一列的所有数据紧挨在一起。

    [1, 2, 3], ['Alice', 'Bob', 'Charlie'], [30, 25, 35]
    

1.2 为什么需要列式存储?(与行式对比)

特性行式存储 (Row-based)列式存储 (Column-based)
适用场景OLTP (在线事务处理):频繁的增、删、改、查(通常按主键查整行)。例如:订单系统、银行交易。OLAP (在线分析处理):海量数据的复杂查询、聚合、统计。例如:数据仓库、BI 报表。
I/O 特点读取整行高效:一次 I/O 即可获取一行所有数据。但查询若只涉及少数几列,会读取大量无关数据。读取少数列高效:只需读取查询所需列的数据,极大减少了 I/O 量。这是 OLAP 场景性能提升的关键。
压缩效率较低:一行内数据类型各异(字符串、数字、日期),相似度低,难以高效压缩。极高:同一列数据类型相同、业务含义相似,数据重复度高,非常适合压缩。常用算法:字典编码、RLE、Delta 编码等。

1.3 为什么性能高?

列式存储的高性能主要源于以下三点:

  1. 最小化 I/O:这是最核心的优势。对于分析类查询,如 SELECT AVG(age) FROM users,列式存储只需读取 Age 这一列的数据,而行式存储则必须扫描每一行的所有数据(包括 UserID 和 Name),I/O 开销可能相差数十甚至数百倍。

  2. 高压缩率:由于同列数据的高度同质性,可以实现非常高的压缩比。这意味着更少的磁盘空间占用和更低的 I/O 带宽需求。数据从磁盘加载到内存的时间也相应减少。

  3. 为向量化执行铺平道路:数据按列连续存放在内存中,这为 CPU 高效处理数据创造了完美条件。这种布局使得后续的向量化执行成为可能。

二、 向量化执行引擎(Vectorized Execution Engine)

如果说列式存储解决了 I/O 瓶颈,那么向量化执行则致力于解决 CPU 计算瓶颈

2.1 什么是向量化执行?

向量化执行是一种数据处理模型,它一次处理一批数据(一个列的片段,称为“向量”或“批”),而不是一次处理一行(Tuple-at-a-time)。

执行模型对比:深入理解性能差异

要理解两种模型的根本区别,我们首先需要了解“查询计划树(Query Plan Tree)”。当数据库执行一条SQL时,会先生成一个由多个**操作符(Operators)**组成的执行蓝图,数据从树的叶子节点(如Table Scan)流向根节点(最终结果)。

数据流向
Aggregate
Filter
Table Scan

现在,我们来看数据是如何在这棵树上流动的。

  • 传统行式/火山模型 (Tuple-at-a-time / 零售模式)
    这是一个典型的“拉动(Pull-based)”模型,数据**一次一行(一个元组)**地在树中被“拉”着向上流动。

    1. 顶层 Aggregate 操作符调用 Filter.next(),说:“给我下一行符合条件的数据”。
    2. Filter 操作符调用 Table Scan.next(),说:“给我下一行数据”。
    3. Table Scan 从表中读出一行数据,返回给 Filter
    4. Filter 检查这一行是否满足条件。如果满足,就将这一行返回给 Aggregate;如果不满足,则回到第2步,继续向 Table Scan 要下一行。

    在这个模型中,处理每一行数据,都会触发一整条从上到下的方法调用链,控制流开销巨大。

  • 向量化执行模型 (Block-at-a-time / 批发模式)
    向量化执行采用的也是“拉动”模型,但拉动的是**“一次一块(Block-at-a-time)”**数据。

    “一个块由固定的一组元组(记录)组成,它代表一组向量,这些向量和列 / 字段有一一对应的关系。向量块是数据的基本单元,它经由执行计划树,从一个操作符流向另一个操作符。”

    1. 顶层 Aggregate 操作符调用 Filter.nextBatch(),说:“给我下一批符合条件的数据”。
    2. Filter 操作符调用 Table Scan.nextBatch(),说:“给我下一批数据”。
    3. Table Scan 从表中一次性读出一个数据块(例如1024行),形成一个 ColumnarBatch,返回给 Filter
    4. Filter 对这一整块数据进行批量处理(例如使用掩码),生成一个新的、只包含符合条件数据的数据块,然后返回给 Aggregate

    在这个模型中,方法调用的开销被均摊到了一个批次的上千行数据上,极大地降低了单位数据的处理成本。

为什么传统行式模型开销大?

看似简单的 while 循环背后,隐藏着巨大的性能开销:

  1. 大量的虚方法调用 (Virtual Function Calls):在 Java 或 C++ 中,算子通常是接口或基类(如 Operator),next() 是一个虚方法。对于一个包含多个算子(扫描、过滤、投影、聚合)的复杂查询,处理每一行数据,都会触发一连串的虚方法调用。这会阻碍 JIT 编译器的内联优化,并导致高昂的动态分派开销。

  2. 频繁的分支预测失败 (Branch Misprediction):循环中的 if 条件(如 WHERE amount > 10)会引入大量分支。现代 CPU 为了提高效率,会进行“分支预测”,提前执行它认为最可能的分支。如果数据分布不均(amount > 10 的结果时真时假),CPU 就会频繁猜错。每次猜错,都会导致整个 CPU 流水线被清空和重建,这会浪费几十个甚至上百个 CPU 周期

  3. 缓存未命中 (Cache Miss):行式数据在内存中通常不是连续的(特别是包含变长字符串时),CPU 在处理数据时需要进行“指针追逐”,这会频繁导致缓存未命中,被迫从慢得多的主内存中读取数据。

2.2 核心概念解析:向量化如何战胜性能瓶颈

向量化执行通过一系列精妙设计,完美地规避了上述问题。

2.2.1 SIMD:用一条指令干多个人的活

SIMD (Single Instruction, Multiple Data),即“单指令,多数据”,是现代 CPU 提供的一种强大的并行计算能力。

  • 是什么:它允许 CPU 用一条指令同时对多个数据执行相同的操作。你可以把它想象成 CPU 内部的一条“流水线”,一次可以处理一整“托盘”的数据,而不是一个一个地处理。
  • 如何实现:CPU 内部有特殊的向量寄存器(如 128位的 SSE 寄存器、256位的 AVX 寄存器)。例如,一个 256 位的 AVX 寄存器可以一次性装入 8 个 32位整数,或 4 个 64位浮点数。CPU 的一条 SIMD 加法指令,就可以瞬间完成这 8 个整数对的相加。
  • 与向量化的关系:列式数据在内存中连续存放,完美契合了 SIMD 的工作模式。引擎可以直接将一个列向量(数组)的一部分加载到向量寄存器中,用 SIMD 指令进行计算,性能提升是数量级的。
2.2.2 掩码/位图:消灭 if 分支的艺术

为了避免分支预测失败带来的巨大成本,向量化引擎采用**掩码(Mask)或位图(Bitmap)**来处理条件逻辑,这是一种将“控制流依赖”转换为“数据流依赖”的技巧。

  • 是什么:一个掩码/位图就是一个布尔值数组或一个比特序列,用于记录一批数据中哪些行符合条件。

  • 如何实现

    1. 生成掩码:不再使用 if 来逐个判断,而是执行一个向量化的比较操作,生成一个掩码。例如,对于 amount > 10,引擎会一次性比较一批 amount 数据,生成一个类似 [true, false, true, ...] 的布尔掩码。
    2. 应用掩码:后续的算子根据这个掩码来只处理有效的数据。例如,聚合算子会根据掩码,只累加那些对应位置为 trueprice 值。
  • 为什么性能高

    • 无分支:整个过程没有 if 跳转,CPU 流水线可以顺畅地执行,不会被打断。
    • SIMD 友好:生成掩码和应用掩码的过程,本身也可以被 SIMD 指令高度优化。

2.3 向量化引擎的设计原理

一个现代的向量化引擎通常包含以下关键设计:

2.3.1 核心技术:字典编码与延迟物化

这两项技术通常结合使用,尤其是在处理字符串等变长、比较耗时的数据类型时。

  1. 字典编码 (Dictionary Encoding)

    • 是什么:一种高效的列压缩技术。它会扫描一列数据(如国家名称),为所有不重复的值创建一个“字典”,并用紧凑的整数ID来替换原始值。
    • 示例
      • 原始列 country: ['美国', '中国', '美国', '日本', '中国']
      • 字典: {0: '美国', 1: '中国', 2: '日本'}
      • 编码后的数据: [0, 1, 0, 2, 1]
    • 优势:极大减少了内存占用;更重要的是,将耗时的字符串比较/哈希操作,转换成了极速的整数操作。
  2. 延迟物化 (Late Materialization)

    • 是什么:在整个查询执行过程中,尽可能地推迟将数据从其压缩或编码形式(如字典ID)转换回原始值的过程。
    • 示例:执行 WHERE country = '美国' 时,引擎首先将 '美国' 在字典中查询到其ID为 0。然后,它在编码后的整数数据 [0, 1, 0, 2, 1] 上执行 ID == 0 的过滤操作。这个过程完全是整数比较,速度极快。
    • 优势:只有在查询的最后一步,当需要向用户展示结果时,引擎才会根据ID(如 0)去字典里查找原始值('美国')。所有中间的过滤、聚合、关联操作都在高效的编码数据上完成,避免了对海量原始数据的昂贵操作。
2.3.2 其他关键设计
  • 批处理模型(Batch Model)

    • 数据以 ColumnarBatchRecordBatch 的形式在算子间流动。一个 Batch 包含多列(ColumnVector),每列包含一批数据(如 1024 或 4096 行)。
  • 标准列式内存格式(如 Apache Arrow)

    • 为了实现高效的跨语言、跨进程数据交换(甚至是零拷贝),业界广泛采用 Apache Arrow 作为内存中的列式标准。
  • 原生算子设计 (Native Operators)

    • 所有的计算算子(如 Filter, Project, Aggregate, Join)都必须重新设计,使其能直接操作 ColumnarBatch

三、总结:

列式存储向量化执行是现代高性能分析引擎的两个核心支柱,它们相辅相成,缺一不可。

  • 列式存储负责在宏观上(磁盘 I/O)减少数据读取量,并提供一种对 CPU 缓存和 SIMD 友好的内存布局。
  • 向量化执行则在微观上(CPU 计算)充分利用这种数据布局,通过批处理、SIMD 指令、无分支计算和延迟物化等技术,将 CPU 的计算能力压榨到极致。

正是这种从存储到计算的全链路优化,才使得像 Spark (with AQE)、StarRocks、ClickHouse 等现代数据系统能够实现惊人的查询性能。


参考资料

  1. 《列式数据库和向量化》 - InfoQ
  2. 《向量化引擎怎么提升数据库性能》 - 墨天轮
http://www.dtcms.com/a/337684.html

相关文章:

  • 无人机行业“黑话”
  • 10CL016YF484C8G Altera FPGA Cyclone
  • Qt第十讲-使用快捷键
  • Mybatis执行sql流程(一)
  • TP6用word文档导入数学公式
  • AI心理助手开发文档
  • [系统架构设计师]未来信息综合技术(十一)
  • Linux unistd.h 包含功能
  • 基于 Ansible 与 Jinja2 模板的 LNMP 环境及 WordPress 自动化部署实践
  • 【C语言】gets和getchar的区别
  • JVM 面试精选 20 题
  • 达梦数据库DCA通关宝典,数据库管理运维学习
  • Java面试题及答案整理(2025年互联网大厂最新版,持续更新)
  • 从数据汇总到高级分析,SQL 查询进阶实战(下篇)—— 分组、子查询与窗口函数全攻略
  • 亲测可用 [安卓]《神秘来电》V1.1无需登入无广告离线打开即用手机模拟发起虚假来电免费版
  • HTTPS面试题(更新中...)
  • 【速通】深度学习模型调试系统化方法论:从问题定位到性能优化
  • Vivado Design Flow
  • 深度学习在订单簿分析与短期价格预测中的应用探索
  • Windows 安装使用 MySQL
  • 44 C++ STL模板库13-容器5-容器适配器-队列(queue)
  • 生鲜冷冻商城系统冷链配送系统功能模块实现
  • Stability AI技术浅析(三):Stable LM模型
  • 【集合框架Map进阶】
  • 【VUE】Vue3 绘制 3D 蓝图利器 Grid Plan
  • 【Java】浅谈ThreadLocal
  • 【WSL2笔记10】WSL-Ubuntu 环境下 ComfyUI 本地部署性能最大化指南
  • 生产环境慎用 context.Background ():你的系统可能在 “空转”
  • CVPR 2025|英伟达联合牛津大学提出面向3D医学成像的统一分割基础模型
  • 【统刷】专题完结,题单汇总