SQL Server列存储实现方案

SQL Server从2012版本开始支持列存储,但2012版本使用列存储会导致表进入只读状态;2014版本使用可更新聚集列存储索引技术解决了只读的问题,使用列存储的表支持修改;2016版本列存储支持操作分析,能够对事务工作负载运行高性能实时分析。以下将对聚集列存储索引技术的实现方案进行分析。

目录

数据存储&划分

行组

列段

物理存储

增量行组

Tuple Mover

数据修改

insert

delete

update

BULK LOADING

优缺点

优点

缺点

疑问


数据存储&划分

行组

关系型数据库中,数据以行写入,列存储首先需要将表的行数据划分为行组,再将行组中的列进行压缩,为了提高性能和压缩率,行组中的行数必须足够大,每个行组最多包含1,048,576 行。

 

SQL Server列存储实现方案

列段

上面说到列存储首先要划分行组,再将行组中的列进行压缩,这里压缩的单位称为列段

SQL Server列存储实现方案

物理存储

聚集列存储索引是整个表的物理存储,磁盘的每个Page仅仅存储来自单列的值。为了减少列段碎片和提升性能,列存储索引可能会将一些数据暂时存储到称为“增量存储”(DeltaStore)的聚集索引中。

SQL Server列存储实现方案

增量行组

上面提到“增量存储”,“增量存储”就是所有增量行组的统称。增量行组通过存储行数据,并在行数达到阈值后将行移入列存储,从而提升了列存储压缩率和性能。在增量行组达到最大行数后,它会关闭。 元组移动进程(tuple-mover) 会检查是否有已关闭行组。将已关闭的行组进行压缩,存储到列存储中。

Tuple Mover

当不断有数据插入列存储表的时候,DeltaStore中的数据会越来越多,因为DeltaStore是B-Tree结构,相对列存储结构性能降低,当DeltaStore中的数据增多时,查询性能会随之降低。

为了解决这个问题,SQL Server 2014引入了一个称为Tuple Mover的后台进程。Tuple Mover后台进程会周期性的检查DeltaStore中已关闭的增量行组,并将其压缩转化为相应的列段。

Tuple Mover进程每次读取一个已关闭的增量行组,在此过程中,并不会阻塞其他进程对DeltaStore的读取操作(但会阻塞并发删除和更新操作)。当Tuple Mover完成压缩处理和转化以后,会创建一个用户可见的新的列段,并使DeltaStore结构中相应的数据不可见(真正的删除操作会等待所有读取该Delta Store进程退出后进行),在这以后的用户读取行为会直接从压缩后的列存储中读取。

 

SQL Server列存储实现方案

 

数据修改

对于更新操作中的删除动作,聚集列存储索引采用的是标记删除的方式,而没有立即物理删除column store中的数据。为了达到标记删除的目的,SQL Server 2014引入了另一个B-Tree结构Delete Bitmap,Delete Bitmap中记录了被标记删除的Row Group对应的RowId,在后续查询过程中,系统会过滤掉已经被标记删除的Row Group。实际上聚集列存储索引本身是不可变的。它是在借助了两个可变结构以后,达到了可更新的目的。

SQL Server列存储实现方案

 

insert

执行insert操作时,insert的数据不会直接进度列存储中,而是先暂存在DeltaStore的增量行组中,DeltaStore中增量行组的数据会在达到行数上限或者列存储索引重建时进入列存储。

 

delete

执行DELETE操作的时候,并不会直接从列存储中直接删除数据,而是往Delete Bitmap结构中插入一条带有rowid的记录,系统会在列存储索引重建(Rebuild)的时候最终删除列存储中的数据。DELETE操作完成后的数据读取操作,SQL Server从列存储中读取数据,然后通过rowid过滤掉在Delete Bitmaps中已经标记删除的数据,最后返回给用户。

update

执行update操作时,实际上会被拆分成delete和insert两个操作,先在Delete Bitmap中插入一条带有旧数据rowid的记录,将旧数据标记为删除,然后在DeltaStore插入新数据。

BULK LOADING

在Bulk LOADING大批次数据导入介绍之前,我们必须要重点介绍几个重要的数字:

  • 102400:数据是否直接进入Column Store的Row Group 行数的最小临界值。
  • 1048576:一个Row Group可以容纳的最大行数。
  • BatchSize:Bulk Insert操作的参数,表示每批次处理的记录数。
  • Rows:需要批量导入聚集列存储索引表的记录总数,Rows应该总是大于等于0的整数。

SQL Server列存储实现方案

如图所示,批量加载时:

  • 不会预先为数据排序。 按接收顺序将数据插入行组。
  • 如果批大小 >= 102400,行将直接插入压缩的行组。 建议选择 >= 102400 的批大小以提高批量导入的效率,因为这样可以避免在后台线程“元组发动机”(TM) 最终将行移到压缩行组之前,将数据行移到增量行组。
  • 如果批大小 < 102,400 或者剩余行数 < 102,400,行会载入增量行组。

优缺点

优点

1. 与面向行的传统存储相比,最多可实现 10 倍的数据仓库查询性能提升

  • 只须读取需要的列。因此,从磁盘读到内存中、然后从内存移到处理器缓存中的数据量减少了。 

  • 列经过了高度压缩。这将减少必须读取和移动的字节数。

  • 大多数查询并不会涉及表中的所有列。 因此,许多列从不会进入内存。这一点与出色的压缩方法相结合,可改善缓冲池使用率,从而减少总 I/O

  • 高级查询执行技术以简化的方法处理列块(称为“批处理”),从而减少CPU使用率(?)

2. 与未压缩数据大小相比,可最多实现 10 倍数据压缩

缺点

1. 更新成本高,更新或删除数据后磁盘空间不会释放,需要重建列存储索引(IO密集型操作)才能回收空间。

2. 整个表仅允许建立一个聚集列存储索引,不允许再有其他的索引

3. 链接服务器不可访问聚集列存储索引表

4. 聚集列存储索引没有对数据做任何排序

5. 聚集列存储索引表不支持游标和触发器

6. 部分数据类型不支持,比如 image/text/varchar(max)/xml等

疑问

1. 每个page仅存储单列的值, 每个page大小为 8KB, 每个列段至少102400个数据,一个列段存储在一个还是多个page中?

   如果一个列段保存到多个page:压缩时一个列段的数据是一起压缩的,如果存储在多个page,读取时则读取多个page来解压缩?

   如果一个列段保存到一个page: 102400个数据,压缩后数据量可能超过8KB如何处理?

2. 删改和更新时产生的垃圾数据,需要进行列存储索引重建才能释放,索引重建时是否允许对表进行操作?索引重建的代价?(暂时不清楚)