SQL Server 列存储索引性能总结(1)——环境准备及简介

列存储是从SQL 2012引入,并在后面发挥非常大性能提升的功能,因为当前工作过程需要使用,并且已经出现了很多问题,所以这里我打算把这两个月的经验总结和分享一下。
这个系列跟前面的有些类似,但是我希望能够把零碎的知识点合在一个系列中,最起码方便自己以后搜索。

环境准备

   本文会使用SQL Server On Linux环境,不过这个问题不大,SQL Server 使用2019版本。
   OS为20G内存,4 core。CentOS 7.7。
   演示数据库使用ContosoRetailDW,由于后续会反复使用这个库,建议保留备份文件,每次操作完就还原。使用大概3千万数据作为测试。部分单独测试会使用新建数据库或者TempDB。
   本系列使用Hyper-V的虚拟机做测试,所以I/O不会很高,它的值不能作为什么衡量标准,但是可以跟其他方式做横向对比。

列存储简介

   列存储需要记住的一些关键点:

  • 列存储设计之初是为了提升数据仓库的性能。
  • 从SQL 2012引入非聚集列存储索引,但是不可更新,禁止修改底层schema,不可使用唯一约束,批执行模式受限等,都使得列存储索引没有很好地推广起来。
  • 从SQL 2014开始,引入了聚集列存储索引。它主要有以下提升:
  1. 可更新。
  2. 可修改schema。
  3. 支持更多数据类型。
  4. 混合执行模式(批和行)。
  5. 支持更多批执行模式。
  6. 更好的压缩效果。
  7. 支持seek操作
  8. 支持和提升bulk insert中的spill。
  • 基本技术点是把数据拆分并存储到不同的行组(row groups),也叫片段(segments),每个片段应该包含大概100万行数据。
  • 每一列都有自己的片段组,是基于列,而不是传统行存储那样基于行。
  • 行组的数据经过编码和压缩后,转成Blobs(因此在SET STATISTICS IO中需要看lob logical reads和lob physical reads)然后存储到SQL Server中。底层存储大小依旧是8K的页。如果一个Blob已经大于1个页,会调用通用的Blobs存储机制。
  • 使用sys.column_store_segments 这个DMV可以查到相关的元数据信息。
  • Delete bitmap:一个非常重要的信息,在前两个月就是因为这个导致我公司的Azure SQL DB 不可用了。这是关于片段中被删除的数据行的信息。后面有机会再讲。
  • 聚集列存储索引的架构总的来说由两部分组成:1. 行组/片段。2. Delta Stores(增量存储)。增量存储是一个常规的b-tree行存储,所以不要以为建了聚集列存储索引之后就一定能用上它的优点。 如果大量的数据都存储在Delta Stores中,那么还是跟传统行存储索引一样。使用DMV:sys.column_store_row_groups 可以查看所有片段和增量存储的信息。DMV中的值,片段的status=3,并且描述是Compressed,Delta Stores的status可能是1或者2,描述为Open/Close。Open意味着增量存储还能添加数据,Close为正在准备转换到片段。
  • 列存储术语:
    Insert:添加数据到当前open的Delta Stores中。
    Delete:如果删除的数据是在片段中,那么Deleted Bitmap会更新并带有对应的row id。如果是在增量存储中,那么直接从B-tree结果中移除。
    Update:实际过程就是先删除再insert。

相关知识

   除了片段、行组、增量存储等概念之外。在使用过程中我还见到了一些术语,这里也列一下做备用:

  1. Tuple Mover:
       当我们插入数据到一个聚集列存储索引表时,也就是Delta Store,对于常规insert(100万行)或者Bulk insert(10万行),到达临界点时就会触发这个Delta Store被填满并关闭,然后创建新的Delta Store供其他insert。
       SQL Server 有一个后台进程会自动检测是否有已经关闭的Delta Store(status=2),如果找到,就会创建一个片段对Delta Store编码和压缩,这个后台进程叫Tuple Mover。默认情况下每5分钟执行一次。
       Tuple Mover不阻塞任何扫描读,但是并行删除和更新必须等待到压缩过程完成。这个阻塞对于高并发高负载的OLTP系统而言是有明显影响的,所以说列存储并非为OLTP设计的。
       已经被压缩的片段/行组可以插入新的行,只要没有达到大概100万的限制。其中一个调用Tuple Mover的方式是执行索引重组(alter index reorganize)。也可以直接alter table rebuild或者alter index rebuild。
  2. Bulk insert:
      导入数据从来都是难点,我所在的项目现在就遇到这个问题。对于聚集列存储索引的Bulk insert而言,10万行是临界点,如果插入90000行数据,那么会创建一个新的Delta Store或者插入到现有的但是能容纳这批数据的Delta Store中。但是如果插入11万行,就会自动转换到片段/行组中。理想的数量应该是超过100万行,这会自动转换到一个不可更新的和已满的片段中。
      Bulk insert本身是非常高效的,它可以全部加载到内存然后进行编码和压缩,然后存储数据到磁盘。前提是内存足够。从SQL 2014起,insert into … select from …命令就被优化成可以使用并行批执行模式的bulk insert操作。
  3. 隔离级别:
      除了Snapshot之外,其他现有隔离级别都支持聚集列存储索引。提到这个是因为Snapshot经常会用在数据仓库中,所以要注意一下。

计划进行的实验

   因为准备可用性组,也就是alwayson,搭建读写分离比较耗时和耗机器,所以暂时不打算做这部分的测试。

  1. (不分区)导入数据到堆表然后创建聚集列存储索引。
  2. (不分区)直接导入数据到聚集列存储索引表。
  3. (不分区)使用Bulk insert导入数据到聚集列存储索引表。
  4. (不分区)使用Bulk insert导入数据到堆表然后创建聚集列存储索引。
  5. (不分区)使用并行插入到堆表然后创建聚集列存储索引。
  6. (分区)导入数据到堆表然后创建聚集列存储索引。
  7. (分区)直接导入数据到聚集列存储索引表。
  8. (分区)使用Bulk insert导入数据到聚集列存储索引表。
  9. (分区)使用Bulk insert导入数据到堆表然后创建聚集列存储索引。
  10. (分区)使用并行插入到堆表然后创建聚集列存储索引。
  11. (不分区,in-memory)导入数据到堆表然后创建聚集列存储索引。
  12. (不分区,in-memory)直接导入数据到聚集列存储索引表。
  13. (不分区,in-memory)使用Bulk insert导入数据到聚集列存储索引表。
  14. (不分区,in-memory)使用Bulk insert导入数据到堆表然后创建聚集列存储索引。
  15. (不分区,in-memory)使用并行插入到堆表然后创建聚集列存储索引。

   下一篇:SQL Server 列存储索引性能总结(2)——获取元数据信息

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章