OLAP:行式存储 & 列式存储

行式存储

  • 传统的关系型数据库,如 Oracle、DB2、MySQL、SQL SERVER 等采用行式存储法(Row-based);
  • 在基于行式存储的数据库中,数据是按照行数据为基础逻辑存储单元进行存储的,一行中的数据在存储介质中以连续存储形式存在。

行式存储缺陷

  • 行式数据库在读取数据的时候,所选择查询的目标即使只涉及少数几项属性,但由于这些目标数据埋藏在各行数据单元中,而行单元往往又特别大,应用程序必须读取每一条完整的行记录,从而使得读取效率大大降低;
  • 对此,行式数据库给出的优化方案是加“索引”;
  • 在 OLTP 类型的应用中,通过索引机制或给表分区等手段,可以简化查询操作步骤,并提升查询效率;
  • 但针对海量数据背景的 OLAP 应用(例如分布式数据库、数据仓库等等),行式存储的数据库就有些“力不从心”了,行式数据库建立索引和物化视图,需要花费大量时间和资源;
  • 因此还是得不偿失,无法从根本上解决查询性能和维护成本等问题,也不适用于数据仓库等应用场景,所以后来出现了基于列式存储的数据库。

列式存储

  • 列式存储(Column-based)是相对于行式存储来说的,新兴的 Hbase、HP Vertica、EMC Greenplum 等分布式数据库均采用列式存储。
  • 在基于列式存储的数据库中,数据是按照列为基础的逻辑存储单元进行存储的,一列中的数据在存储介质中以连续存储形式存在。

列存储优点

  • 列存储在写入效率、保证数据完整性上都不如行存储,它的优势是在读取过程,不会产生冗余数据,这对数据完整性要求不高的大数据处理领域,比如互联网,犹为重要。
  • 查询过程中,可针对各列的运算并发执行(SMP),在内存中聚合完整记录集,可能降低查询响应时间;
  • 可在数据列中高效查找数据,无需维护索引(任何列都能作为索引),查询过程中能够尽量减少无关IO,避免全表扫描;
  • 因为各列独立存储,且数据类型已知,可以针对该列的数据类型、数据量大小等因素动态选择压缩算法,以提高物理存储利用率;
  • 如果某一行的某一列没有数据,那在列存储时,就可以不存储该列的值,这将比行式存储更节省空间。

数据压缩

  • 由于同一个数据列的数据重复度很高,因此,列式数据库压缩时有很大的优势。
  • Google Bigtable 列式数据库对网页库压缩可以达到15倍以上的压缩率。
  • 另外,可以针对列式存储做专门的索引优化。

位图索引

  • 比如,某表性别列只有两个值,“男”和“女”,可以对这一列建立位图索引;
  • 如下图所示:
    • “男” 对应的位图为100101,表示第 1、4、6 行值为 “男”;
    • “女” 对应的位图为011010,表示第 2、3、5 行值为 “女”;
  • 如果需要查找男性或者女性的个数,只需要统计相应的位图中1出现的次数即可。

  • 另外,建立位图索引后 0 和 1 的重复度高,可以采用专门的编码方式对其进行压缩。

数据查询

  • 查询关键步骤如下:
    • 去字典表里找到字符串对应数字(只进行一次字符串比较)。
    • 用数字去列表里匹配,匹配上的位置设为 1。
    • 把不同列的匹配结果进行位运算得到符合所有条件的记录下标。
    • 使用这个下标组装出最终的结果集。

行式存储 VS 列式存储

  • 行式存储倾向于结构固定,列式存储倾向于结构弱化。
  • 行式存储一行数据只需一份主键,列式存储一行数据需要多份主键。
  • 行式存储存的都是业务数据,列式存储除了业务数据外,还要存储列名。
  • 行式存储更像一个Java Bean,所有字段都提前定义好,且不能改变;列式存储更像一个Map,不提前定义,随意往里添加key/value。

特性对比

  • 传统行式数据库的特性如下:
    • 数据是按行存储的;
    • 没有索引的查询使用大量 I/O,一般的数据库表都会建立索引,通过索引加快查询效率;
    • 建立索引和物化视图需要花费大量的时间和资源;
    • 面对查询需求,数据库必须被大量膨胀才能满足需求。
  • 列式数据库的特性如下:
    • 数据按列存储,即每一列单独存放;
    • 数据即索引;
    • 只访问查询涉及的列,可以大量降低系统 I/O;
    • 每一列由一个线程来处理,即查询的并发处理性能高;
    • 数据类型一致,数据特征相似,可以高效压缩;
      • 比如有增量压缩、前缀压缩算法都是基于列存储的类型定制的,所以可以大幅度提高压缩比,有利于存储和网络输出数据带宽的消耗。

数据写入

  • 行存储的写入是一次完成的,如果这种写入建立在操作系统的文件系统上,可以保证写入过程的成功或者失败,数据的完整性因此可以确定;
    • 列存储由于需要把一行记录拆分成单列保存,写入次数明显比行存储多
    • 意味着磁头调度次数多,而磁头调度是需要时间的,一般在1ms~10ms;
    • 再加上磁头需要在盘片上移动和定位花费的时间,实际消耗更大;
    • 所以,行存储在写入上占很大的优势。
  • 数据修改,实际也是一次写入过程;
    • 不同的是,数据修改是对磁盘上的记录做删除标记;
    • 行存储是在指定位置写入一次,列存储是将磁盘定位到多个列上分别写入,这个过程仍是行存储的列数倍;
    • 所以,数据修改也是以行存储占优。

数据读取

  • 数据读取
    • 行存储通常将一行数据完全读出,如果只需要其中几列数据的情况,就会存在冗余列,出于缩短处理时间的考量,消除冗余列的过程通常是在内存中进行的;
    • 列存储每次读取的数据是集合的一段或者全部,不存在冗余性问题;
  • 数据分布
    • 由于列存储的每一列数据类型是同质的,不存在二义性问题。
      • 比如说某列数据类型为整型(int),那么它的数据集合一定是整型数据。
      • 这种情况使数据解析变得十分容易。
    • 相比之下,行存储则要复杂得多,
      • 在一行记录中保存了多种类型的数据,数据解析需要在多种数据类型之间频繁转换,这个操作很消耗 CPU,增加了解析的时间。
      • 所以,列存储的解析过程更有利于分析大数据。

适用场景

  • 行式存储的适用场景
    • 适合随机的增删改查操作;
    • 需要在行中选取所有属性的查询操作;
    • 需要频繁插入或更新的操作,其操作与索引和行的大小更为相关。
  • 列式存储的适用场景
    • OLAP
      • 一般来说,一个 OLAP 类型的查询可能需要访问几百万甚至几十亿个数据行,且该查询往往只关心少数几个数据列。
      • 例如,查询今年销量最高的前20个商品,这个查询只关心三个数据列:时间(date)、商品(item)以及销售量(sales amount)。
      • 商品的其他数据列,例如商品URL、商品描述、商品所属店铺,等等,对这个查询都是没有意义的。
      • 列式数据库只需要读取存储着“时间、商品、销量”的数据列,而行式数据库需要读取所有的数据列。
      • 因此,列式数据库大大地提高了 OLAP 大数据量查询的效率。   
    • OLAP & OLTP
      • 很多列式数据库还支持列族(column group,Bigtable系统中称为locality group),即将多个经常一起访问的数据列的各个值存放在一起。
      • 如果读取的数据列属于相同的列族,列式数据库可以从相同的地方一次性读取多个数据列的值,避免了多个数据列的合并。
      • 列族是一种行列混合存储模式,这种模式能够同时满足 OLTP 和 OLAP 的查询需求。   
    • 数仓
      • 对于数据仓库和分布式数据库来说,大部分情况下它会从各个数据源汇总数据,然后进行分析和反馈,其操作大多是围绕同一列属性的数据进行的,而当查询某属性的数据记录时,列式数据库只需返回与列属性相关的值,在大数据量查询场景中,列式数据库可在内存中高效组装各列的值,最终形成关系记录集,因此可以显著减少IO消耗,并降低查询响应时间,非常适合数据仓库和分布式的应用。