成为数据治理专家:行式存储VS列式存储

成为数据治理专家:行式存储VS列式存储

行式存储

Row-based storage storesatable in a sequence of rows

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

列式存储

Column-based storage storesatable in a sequence of columns

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

简单来说 行式存储 的存储单位是行,列式存储 的基本单位是列。似乎有点听君一席话如听一席话的感觉吧,不要打我下面我们详细介绍。

行存储、列存储对比

在数据写入上的对比

  • 行存储的写入是一次完成。如果这种写入建立在操作系统的文件系统上,可以保证写入过程的成功或者失败,数据的完整性因此可以确定。

  • 列存储由于需要把一行记录拆分成单列保存,写入次数明显比行存储多(意味着磁头调度次数多,而磁头调度是需要时间的,一般在1ms~10ms),再加上磁头需要在盘片上移动和定位花费的时间,实际时间消耗会更大。所以,行存储在写入上占有很大的优势。

  • 还有数据修改,这实际也是一次写入过程。不同的是,数据修改是对磁盘上的记录做删除标记。行存储是在指定位置写入一次,列存储是将磁盘定位到多个列上分别写入,这个过程仍是行存储的列数倍。所以,数据修改也是以行存储占优。

行式存储的优势是写操作。基于行存的数据,在写入、更新、删除时,只需要找到这一行进行一次操作即可。
在读取方面,哪怕只想读取其中的一列都需要找到整行,再从整行中进行读取。

在数据读取上的对比

  • 数据读取时,行存储通常将一行数据完全读出,如果只需要其中几列数据的情况,就会存在冗余列,出于缩短处理时间的考量,消除冗余列的过程通常是在内存中进行的。

  • 列存储每次读取的数据是集合的一段或者全部,不存在冗余性问题。

  • 两种存储的数据分布。由于列存储的每一列数据类型是同质的,不存在二义性问题。比如说某列数据类型为整型(int),那么它的数据集合一定是整型数据。这种情况使数据解析变得十分容易。相比之下,行存储则要复杂得多,因为在一行记录中保存了多种类型的数据,数据解析需要在多种数据类型之间频繁转换,这个操作很消耗CPU,增加了解析的时间。所以,列存储的解析过程更有利于分析大数据。

  • 从数据的压缩以及更性能的读取来对比,列存储由于是同质性数据,可以针对性的进行数据压缩和查询性能提升。

如下图:首先将Customes Name列及Material列做逻辑化索引标识,查询时分别匹配Materia=Refrigerator及Customes Name=Miller的数据,然后做交叉匹配。

列式存储的优势是读取。在写入、更新、删除时,需要找到多个列极端情况下是所有的列,要对多个列分别进行操作,写入的次数变得较多。
在读取方面,列式存储非常的灵活,只需要找到要读取的某几个列即可。

代码模拟行存和列存

下面我们用简单的代码对行存储和列存储进行模拟。

模拟行存:

private static void rowBased() {
    Map<Integer , Map<String , Object>> rows = new HashMap<>();

    Map<String , Object> row1 = new HashMap<>();
    row1.put("id" , 1);
    row1.put("firstname" , "smith");
    row1.put("lastname" , "joe");
    row1.put("age" , 12);

    Map<String , Object> row2 = new HashMap<>();
    row2.put("id" , 2);
    row2.put("firstname" , "jones");
    row2.put("lastname" , "mary");
    row2.put("age" , 12);

    rows.put(1 , row1);
    rows.put(2 , row2);

    // 更新
    // 1. 找到行,更新
    Map<String, Object> targetRow = rows.get(1);
    targetRow.put("lastname" , "Lee");
    targetRow.put("firstname" , "DevX");
    targetRow.put("age" , 22);


    // 删除
    row1.remove(1);

    // 读取 lastname 是 mary 数据
    // 如果还想读取别的列,必须在找到这一行后再进行读取
    for (Map.Entry<Integer, Map<String, Object>> entry : rows.entrySet()) {
        Map<String, Object> row = entry.getValue();
        if ("mary".equals(row.get("lastname"))) {
            System.out.println(row);
            for (Map.Entry<String, Object> columns : row.entrySet()) {
                 if ("age".equals(columns.getKey())) {
                    System.out.println("age = " + columns.getValue());
                }
            }
        }
    }
}

模拟列存:

private static void columnBased() {

    // key 是 id value 是对应列的值
    Map<Integer , Integer> idColumns = new HashMap<>();
    idColumns.put(1 , 1);
    idColumns.put(2 , 2);

    Map<Integer , String> firstNameColumns = new HashMap<>();
    firstNameColumns.put(1 , "smith");
    firstNameColumns.put(2 , "jones");

    Map<Integer , String> lastNameColumns = new HashMap<>();
    lastNameColumns.put(1 , "joe");
    lastNameColumns.put(2 , "mary");

    Map<Integer , Integer> ageColumns = new HashMap<>();
    ageColumns.put(1 , 12);
    ageColumns.put(2 , 16);

    // 更新
    firstNameColumns.put(1 , "Lee");
    lastNameColumns.put(1 , "DevX");
    ageColumns.put(1 , 22);

    // 删除
    idColumns.remove(1);
    firstNameColumns.remove(1);
    lastNameColumns.remove(1);
    ageColumns.remove(1);

    // 基于列式存储可以并行的读取多个不同的列
    // 读取 lastname 是 mary 数据
    for (Object value : lastNameColumns.values()) {
        if ("mary".equals(value)) {
            System.out.println(value);
        }
    }

    // 读取 firstname 是 DevX 数据
    for (Object value : firstNameColumns.values()) {
        if ("DevX".equals(value)) {
            System.out.println(value);
        }
    }
}

行存储的写入是一次性完成,消耗的时间比列存储少,并且能够保证数据的完整性,缺点是数据读取过程中会产生冗余数据,如果只有少量数据,此影响可以忽略;数量大可能会影响到数据的处理效率。

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

Row-basedColumn-based
写入每一行的所有字段都存在一起,优点:对数据进行插入和修改操作很方便当一条新数据到来,每一列单独存储,缺点:插入和修改操作麻烦
查询查询时即使只涉及某几列,所有数据也都会被读取;优点:适合随机查询;在整行的读取上,要优于列式存储;缺点:行式存储不适合扫描,这意味着要查询一个范围的数据查询时只有涉及到的列会被读取;缺点:查询完成时,被查询的列要重新进行组装
寻道范围读取数据的时候硬盘寻址范围很大由于仅对需要的列进行查找,因此硬盘寻道范围小
索引缺点:要加速查询的话需要建立索引,建立索引需要花费很多时间优点:任何列都能作为索引(每一列单独存储,查询个别列的时候,可以仅读取需要的那几个列,相当于为每一列都建立了索引)
压缩缺点:不利于压缩把一列数据保存在一起,而一列的数据类型相同 ;优点:利于压缩
空间按行存储,不利于压缩,压缩比较差,占空间大列式存储的时候可以为每一列创建一个字典,存储的时候就仅存储数字编码即可,降低了存储空间需求
聚合不利于聚合操作按列存储,利于数据聚合操作
使用场景OLTP(存储关系型数据,用于使用数据的时候需要经常用到数据之间的依赖关系的场景,即读取的时候需要整行数据或者整行中大部分列的数据,需要经常用到插入、修改操作)OLAP(分布式数据库和数据仓库,适合于对大量数据进行统计分析,列与列之间关联性不强,仅进行插入和读取操作的场景)

行式存储、列式存储的主流数据库

行式存储数据库

  • MySQL
  • Oracle
  • SQLServer
  • DB2
  • PostgreSQL

行式存储数据库一般都已 OLTP 见长。

列式存储数据库

  • Druid
  • Kudu
  • Clickhouse
  • StarRocks
  • Hbase
  • HP Vertica
  • EMC Greenplum

列式存储数据库一般都已 OLAP 见长。

行列混存数据库

  • Oracle 双模式架构

对于Oracle而言,如果开启了In-Memory选项的话,会提供双模式架构:

  • 行式模式:磁盘(数据文件)和高速缓存(buffer cache)中,能够快速访问记录中的所有列,适合DML,满足OLTP类型应用。

  • 列式模式:根据设置加载到In-Memory Area中,适合数据分析和聚合等操作,满足OLAP类型的查询需求。

  • TiDB
  • Hologres
  • PolarDB

本文简单介绍了行存储和列存储,对于两种存储方式来说并没有孰优孰劣,只是擅长的方向不同而已。在实际系统中往往是两者配合使用。
在做技术选型时,需要深入了解业务场景特点,进行模拟测试后,根据评测结果选择合适的技术。

1285229-20210328210436513-2055076251.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值