跳到主要内容

列式存储原理

问题

什么是列式存储?为什么 OLAP 系统普遍采用列式存储?

答案

行式存储 vs 列式存储

维度行式存储列式存储
存储方式按行存储按列存储
读取粒度整行(所有列)只读需要的列
压缩率低(混合类型)高(同类型)
聚合查询慢(全行扫描)快(只扫描目标列)
点查/更新快(一次 IO 读整行)慢(需组装多列)
典型系统MySQL、PostgreSQLClickHouse、Doris
适合场景OLTPOLAP

IO 优势示例

假设表有 100 列、1 亿行:

SELECT SUM(amount), COUNT(*) FROM orders WHERE date = '2024-01-01';
存储方式读取数据量
行式存储100 列 × 1 亿行 = 全量数据
列式存储2 列(amount + date)= 2% 数据
核心优势

列式存储只扫描查询涉及的列,在宽表场景下 IO 减少可达 50~100 倍

压缩优势

同一列的数据类型相同,压缩效果远好于行式存储:

压缩算法适用列类型压缩率
LZ4通用,速度快2~4x
ZSTD通用,压缩率更高3~8x
Delta时间戳、递增 ID10~50x
Dictionary低基数字符串(如 city5~20x
Run-Length排序后重复值多10~100x

向量化执行

向量化执行引擎一次处理一批数据(通常 1024~4096 行),充分利用 CPU Cache 和 SIMD 指令:

  • CPU Cache 友好:连续内存访问,减少 Cache Miss
  • SIMD 指令:单条指令并行处理多个数据
  • 减少虚函数调用:批量处理减少函数调用开销

常见列式存储格式

格式使用者特点
ParquetSpark、Hive、Doris嵌套结构、行组内列式
ORCHive、Presto轻量级索引、ACID 支持
Arrow内存列式格式跨语言零拷贝
自定义格式ClickHouse高度优化,深度集成
Parquet 文件结构

Parquet 按**行组(Row Group)+ 列块(Column Chunk)**组织:

  • 行组:一组行的集合(通常 128MB)
  • 列块:行组中某一列的数据
  • 页(Page):列块内的最小读取单元

常见面试问题

Q1: 列式存储为什么不适合 OLTP?

答案

OLTP 场景特点是高并发单行读写

  • 读取一行需要从多个列文件中分别读取再组装,IO 次数多
  • 插入/更新一行需要写入多个列文件,写放大严重
  • 行式存储一次 IO 即可读写完整行,更适合 OLTP

Q2: Parquet 和 ORC 怎么选?

答案

维度ParquetORC
生态Spark 原生Hive 原生
嵌套结构✅ 原生支持⚠️ 有限支持
压缩率略高于 Parquet
索引统计信息轻量级索引
推荐Spark/Doris 场景Hive 场景

Q3: 什么是编码(Encoding)和压缩(Compression)的区别?

答案

  • 编码:利用数据特征进行转换(如 Delta、Dictionary、RLE),通常在写入时即完成
  • 压缩:对编码后的数据进一步压缩(如 LZ4、ZSTD),减小存储空间
  • 两者可叠加使用,先编码再压缩,效果最佳

相关链接