知识卡片

列式存储把按行存储改为按列存储天然契合分析查询与压缩

结构图卡

内容

[[星型模式用事实表与维度表拆分事件本身和事件属性]]里事实表虽然常有上百列,但典型 分析查询一次通常只访问其中4-5列(很少用SELECT *)。如果沿用OLTP常见的面向行存储 (一行的所有值相邻存储),即使只需要3列,存储引擎也不得不把每一行上百个属性全部 从磁盘加载进内存再过滤掉不需要的部分,代价很高。面向列存储反其道而行:不把一行的 值存在一起,而是把同一列的所有值存在一起(比如每列单独一个文件),这样查询只需要 读取和解析用到的那几列,省下大量无谓的I/O。列式存储依赖每个列文件按相同的行顺序 排列,重组某一行数据时,把每个列文件里第k项取出拼在一起即可。列存储还天然适合 压缩:同一列的值往往高度重复(比如列里不同值的种类相比总行数少得多)。一种在数据 仓库里特别有效的技术是位图编码:把一个有n个不同值的列拆成n个独立位图,每行占一位, 该行取该值则为1,否则为0;如果n不大,可以每行存一位,n更大时位图会很稀疏,可以再 用游程编码进一步压缩。这种位图结构还天然契合分析常见的查询模式——WHERE product_sk IN (30,68,69)只需加载这三个值的位图做按位OR;WHERE product_sk=31 AND store_sk=3只需加载两个位图做按位AND,因为各列位图按相同行顺序排列,第k位 天然对应同一行。此外,选定一列(如日期)做主排序键还能让该列压缩率最高(长串重复 值),同时帮助查询优化器只扫描目标日期范围的行,把范围过滤和压缩收益结合起来。

结构图

flowchart LR
    A[面向行存储] -->|一行全部属性相邻| A1[分析查询需加载整行, 再过滤掉多余列]
    B[面向列存储] -->|同列所有值相邻, 各列独立文件| B1[只读取查询用到的几列]
    B1 --> B2[同列值高度重复, 适合位图编码压缩]
    B2 --> B3[WHERE IN: 多个位图按位OR]
    B2 --> B4[WHERE AND: 多个位图按位AND]
    B --> B5[选定主排序列: 提升该列压缩率+支持范围过滤]

参考来源

- 位置:《数据密集型应用系统设计》第三章《存储与检索》"列存储""列压缩""列存储中的 排序顺序"(源文件:_epub-src/ch3_split_003.html) - 结论依据:原文说明面向行存储需加载整行才能过滤出少数列,面向列存储只读取所需 列,并详述位图编码压缩机制及其如何支持IN和AND查询的按位运算,以及排序键对压缩 和范围过滤的双重好处,直接支撑本卡片的结构梳理。 - 原始内容:面向列的存储背后的想法很简单:不要将所有来自一行的值存储在一起,而是 将来自每一列的所有值存储在一起……现在我们可以拿一个有n个不同值的列……转换成n个 独立的位图……WHERE product_sk IN(30,68,69)……计算三个位图的按位或。