列存储模式
- 列存储模式概念
列存储模式也可以称为面向列的存储模式,在面向列的存储模式中,属于不同列或列族的数据存储在不同的文件中,这些文件可以分布在不同的位置上,甚至不同的节点上。
相比之下,在面向行的存储模式中,数据以行的方式整合在一起,数据行中的每个字段都在一起存储。
姓名 | 性别 | 年龄 |
Alice | 女 | 18 |
Bob | 男 | Null |
Chris | Null | 20 |
Dylan | 男 | 21 |
行存储
列1 | |
姓名 | 年龄 |
Alice | 18 |
Chris | 20 |
Dylan | 21 |
列2 | |
姓名 | 性别 |
Alice | 女 |
Bob | 男 |
Dylan | 男 |
列存储
列存储的核心技术就是基于垂直分区的存储设计和访问模式
- 列存储模式的优缺点
优点:
- 查询速度快 只需查询读取涉及关系中某些数据列, 避免无关提取。
- 极高的装载速度 (最高可以等于所有硬盘IO总和
- 适合大量的数据而不是小数据
- 实时加载数据仅限于增加(删除和更新需要解压缩Block 然后计算然后重新压缩储存)
- 高效的压缩率,不仅节省储存空间也节省计算内存 和CPU。
- 非常适合做聚合操作
缺点:
- 存储的效率。但在进行数据插入的操作时,不可使用UPDATE和INSERT操作,只可选择APPEND追加的方式进行实现
- 不适合扫描小量数据
- 不适合随机的更新
- 批量更新情况各异,有的优化的比较好的列式数据 库(比如Vertica)表现比较好,有些没有针对更新 的数据库表现比较差。
- 不适合做含有删除和更新的实时操作。
- 列式存储引擎的适用场景
1、查询过程中,可针对各列的运算并发执行(SMP),最后在内存中聚合完整记录集,最大可能降低查询响应时间;
2、可在数据列中高效查找数据,无需维护索引(任何列都能作为索引),查询过程中能够尽量减少无关IO,避免全表扫描;
3、因为各列独立存储,且数据类型已知,可以针对该列的数据类型、数据量大小等因素动态选择压缩算法,以提高物理存储利用率;如果某一行的某一列没有数据,那在列存储时,就可以不存储该列的值,这将比行式存储更节省空间。
- 列存储中常用的数据压缩算法
4.1、Run-Length Encoding
核心思想是将一个有序列中相同的列属性值转化为三元组(列属性值,在列中第一次出现的位置,出现次数),适用于列有序或者列可以转化为有序且列中distinct值较少的情况。
其中一个排好序的列仅包含两个distinct值,通Run-Length Encoding,整个列使用两个简单的三元组就可以表示了。使用这种算法,一个列可以转化为多个三元组,通过在这些三元组上构建B树索引就可以轻松地实现对该列的管理。
4.2、Bit-Vector Encoding
核心思想是将一个列中所有相同列属性的值转化为二元组(列属性值,该列属性值出现在列中位置的Bitmap),适用于列无序且无法转化为有序但列中distinct值较少的情况。
如上图所示,其中一个无序的列仅包含两个distinct值,8000这个值分别出现在列中的0、3、4、6四个位置,3000这个值分别出现在列中的1、2、5三个位置,使用位图便可以表示出来,通过Bit-Vector Encoding,整个列使用两个简单的二元组就可以表示了。使用这种算法,一个列可以转化为多个二元组,通过在这些二元组上构建B树索引就可以轻松地实现对该列的管理。此外,如果列中distinct值较少,那么二元组中的位图就会很稀疏,所以位图还可以使用上面的Run-Length Encoding进行二次压缩。
4.3、Dictionary Encoding
核心思想就是利用简短的编码代替列中某些重复出现的字符串,通过维护一个编码与被替代字符的映射就可以快速确定编码指向的是哪个字符串,这个映射也就是所谓的Dictionary,适用于列中存在很多相同字符串的情况。这里不妨以Google在VLDB2012上发表的论文为例来说明Dictionary Encoding的妙处。图三给出了该论文中一个示例(原论文中示例有误,即图中红圈标注处,可忽略),其中一个名为search_string的列被分成了三个块(chunk),在每个块中都存在重复的查询字符串。
为了压缩每个数据块的大小,首先创建一个全局字典表global-dictionary,该表中存储search_string列中所有的distinct字符串,且每个字符串均对应一个全局id,譬如amazon的全局id就是它前面的1。其次,每个块中也创建一个块字典表chunk-dict,该表中存储了块中所有的distinct字符串在global-dictionary中的全局id,且每个全局id均对应了一个块id,通过这种二级字典表的方式,一个字符串就可以通过全局字典表映射到一个全局id,再通过块字典表映射到一个块id。所以,此时块中也不再存储真正的查询字符串,而是存储查询字符串对应的块id,即图中所示的elements。譬如要查询chunk 0中第4个element真正代表的值时,需要使用该element的值4到块字典表中查询得到它对应的全局id为12,然后在使用12到全局字典表中查询得到12对应的字符串是“yellow pages”。使用这种算法,一个存储了查询字符串的列就转化成了存储32位整型值的列,数据空间大大缩小。