列式存储 | 青训营

198 阅读5分钟

C-Store: a column-oriented DBMS

一种面向列的DBMS,其结构是为了减少每个查询的磁盘访问次数

介绍了一种针对读优化的关系型数据库

  • 列存储
  • 在查询处理时对数据进行编码和压缩
  • 存储具有重叠部分的对列的投影 projections
  • 提供高可用的事务和只读事务的快照隔离
  • 使用位图索引来优化 B-tree 结构

2 数据模型

C-Store 支持标准的关系型 logical data model:一个数据库包含了多张表,每张表是attribute(column) 的结合,有主键和外键

大部分行存数据库直接按照 logical data model 实现了物理数据模型,但是 C-Store 中只实现了 projections(projections 包含一个表的一个或多个字段的数据,也可包含其他表的列,只要 projection 所属于的锚表 anchor table 中含有其他表的外键)

projection 与锚表的行数相同

EMP1 (name, age, DEPT.floor | DEPT.floor)

对表 t 的第 i 个 projection 记为 ti,后面是 projection 里面的字段名称

来自其他表的属性的前面加有它们来自的逻辑表的名称

投影中的元组是按列顺序存储的,因此,如果一个投影中有K个属性,则会有K个数据结构,每个结构存储一列,每一列按照相同的 sort key 排序,projection 括号中竖线后边的键为 sort key(sort key 可以是投影中的任意一列或多个列。投影中的元组按 sort key 从左到右的顺序排序)

每个 projection 被水平划分为一个或多个 segments, 每个段有一个 segment identifier (Sid > 0)。C-Store只支持对投影基于 sort key 的值进行分段,因此,每个 segment 都对应 sort key 的一个范围 key range。key range 的集合就划分了 key space。

数据库中每个表的每一列都至少要存储在一个 projection 中,才能响应所有的 SQL 请求。

C-Store 必须能够使用存储的 segments 来重建原始的行表,需要将不同的 projections 的 segments join 起来:

  • Storage Key

    • segment 中的一行称为 record 或 tuple
    • 每个段中每一列的数据都会有一个序号 storage key(SK),同个段的数据中,如果 storage key 相等,则说明它们在逻辑中是同一行的
    • RS 组件中的 SK 不是物理存储的而是可从 tuple 在列中的物理位置推导出来
    • WS 组件中的 SK 是物理存储的,且里面的 SK 值比 RS 中的 SK 值都大
  • join indices

    • 假设 T1、T2 是同个逻辑表的两个 projections,那么从 T1 映射到 T2 的 join index 中,会按 T1 中的数据顺序,记录每条数据在 T2 的位置,格式为(s: SID in T2, k: Storage Key in Segment s)
    • 将每个列都存储在多个 projections 中,以减少 join index 的数量。这是因为 join index 的存储和维护成本非常高,对 projections 的修改都需要更新指向它或指向它的每个 join index。
    • 数据库中 projection 的每个段以及对应的 join index 都要存储在多个节点中,用以实现 K-safe。

3 RS

RS 是面向读优化的列存储,projection 的每一列都按其 sort key 的顺序存储

RS 中的 storage key 是数据在 segment 中的序号

3.1 编码方案

RS 中的列有四种压缩编码方案,一个列的编码方案的选择依赖于它的排序顺序和它所包含的不同值的比例

  • Type 1:Self-order, few distinct values

    • 使用Type 1编码的列由一个三元组序列 (v, f, n) 来表示,v 是存储在列中的值,f 是列中 v 第一次出现的位置,n 是 v 在列中出现的次数
    • 对于 self-order 的列,列中每个 distinct 的值都有一个三元组
    • 为了支持查询, c-store 对这个结构支持了 B 树索引,加快了对这个内容的查找
    • 由于不会对 RS 做在线的更新,可以实现稠密的索引
  • Type 2: foreign-order, few distinct values

    • 使用Type 2编码的列由元组(v, b) 来表示,v 是存储在列中的值,b 是位图(显示 v 在列中的位置)
    • 每个位图都是稀疏的,所以会在进行 length encoded 来节约空间
    • 为了快速找到列中的第 i 个值,这里用 B 树加以优化,将列中的位置映射到列中的值。这个 B 树称为 offset indexes
  • Type 3: self-order, many distinct values

    • 将列中的每个值表示为列中前一个值的增量
  • Type 4: foreign-order, many distinct values

    • 不编码

4 WS

WS也是一个列存储,并实现了与RS相同的物理DBMS设计

然而,存储表示是完全不同的,因为WS必须有效地执行 update transaction

storage key 是显式存储在每个 WS 的 segment 中的,向表 T 中插入一个数据就赋给它一个唯一的 SK,执行引擎会确保每个存储了该 logical tuple 的 projection 中都记录了这个 SK,这个 SK 其实是一个整数,比数据库中最大的 segment 中的 records 个数的值还要大

WS 采用与 RS 相同的方式进行水平分片,因此,RS 段和 WS 段之间存在一对一的映射关系。(sid, storage key) 能标识这两者中的一条记录

因为 WS 比 RS 小得多,所以不必压缩 WS 的数据

WS 的 projection 中的每一列都表示为(v, sk)的集合, v 是列的数据的值,sk 是其对应的 storage key,并为每列的 sk 建了 b 树索引。

每个 projections 的 sort key 会被表示为(s, sk), s 是 sort key 的值,sk 是 s 第一个出现的位置的 storage key。同样,也为(s, sk)集合的 s 建了 b 树索引。

因此当我们要使用 sort key 来查找数据时,通过这个 b 树找到目标的 storage key,再由前面的 b 树找到 projection 中目标的其他字段

projections 会被分片成很多段,有的在 WS,有的在 RS。因此 join index 的 (sid, storage_key) 指向的记录可能在 WS 也可能在 RS。