predicate 列存储索引扫描_解读SQL Server 2014可更新列存储索引——存储机制

概述

SQL Server 2014被号称是微软数据库的一个革命性版本,其性能的提升的幅度是有史以来之最。

可更新的列存储索引作为SQL Server 2014的一个关键功能之一,在提升数据库的查询性能方面贡献非常突出。据微软统计,在面向OLAP查询统计类系统中,相比其他SQL传统版本的数据库,报表查询的性能最大可提升上十倍。

下面我们从存储的角度来了解下SQL Server 2014的可更新列存储索引。

什么是列存储

微软为了提升SQL Server的查询性能,更好的支持大数据分析,早在SQL Server 2012中就引入了列存储的技术,

列存储的本质是将一个张表按照不同的列拆分,然后每一列单独存储,这样一来,存储的单位由原来的每一行变成了每一列。

像下面这张表,表中10个列分布在10个页面中,在page1中包括了表中ROW1到ROWn中列C1的数据,在page2中包括ROW1到ROWn中列C2的数据,后面依次类推。

这样做的好处就是:

更好的数据压缩,减少磁盘的空间占用

数据的相似性越高、重复的值越多,压缩的效果就越明显。

列存储中的每一列数属于同一种数据类型,表达的是同一个数据概念(比如都是性别),内容重复度很高,因此相比行压缩和页压缩而言,压缩效率会更好。

这样一来不仅可以有效节省磁盘空间,而且可以在同样的内存中记录更多的数据,提升查询的性能。

提升查询的性能

在一个查询中,我们往往只是想获取表中我们感兴趣的一列或者某几列的数据,

传统的查询做法必须把表中的所有的数据都扫描一遍,从而筛选出这些指定的列,

当表中包含的列比较多、数据量时,这种查询的效率的就会很低,

但如果使用了列存储,因为每一个列都是集中且彼此独立地存储,查询时只需要扫描这些指定列所在的存储区域就好了,不需要读其他不需要列的数据。查询的范围就小了很多。

比如这个查询:

select c1 from myuser1

因为mytable表中所有的C1列都是集中存储的,一次查询只扫描C1列的存储区域就好了。

什么是列存储索引

在传统的表上创建列存储索引后便可实现表的列存储。

在SQL Server 2014中,有两种列存储索引:列非聚集索引和列聚集索引。

(1)、列非聚集索引的特点

跟非聚集索引一样,创建列非聚集索引时必须为索引列创建一个副本,占用额外的磁盘空间,不过因为数据压缩的原因,其占用的空间会比较小

列非聚集索引的表是不可以更新的

备注:SQL Server 2012上只能建列非聚集索引,不能建列聚集索引。

(2)、列聚集索引的特点

索引页就是数据页,高压缩率大大减少磁盘空间的占用

可以对表进行更新

列聚集索引必须是表的唯一索引,如果表中存在聚集索引或者非聚集索引,则必须删除原来的索引才能创建列存储索引

只有企业版、开发版、评估版中才能使用

不会改变列的物理存储顺序,其目的主要是为了提升性能和实现较高的数据压缩

备注:读者可以访问此地址,了解更多关于列聚集索引相关特性及使用限制。另外,下文如无特别说明,所描述的列存储均是包含列聚集索引的存储结构。

创建列聚集索引

列存储索引创建时不需要指定列名,索引一旦创建完成,表中所有的行就会以列的方式存储。

CREATE CLUSTERED COLUMNSTORE INDEX ci_myUser ONMyUser1;GO

为了更好的说明在列聚集索引创建时“行表”——>“列存储”的过程,我做了一张简图,并对每个序号的说明如下:

①:首先,表被拆分成一个或者多个行组(ROW GROUP);

一般而言,每个行组中的行数必须满足最小为102,400、最大1,048,576后才能转换成列存储。但如果直接在表上创建列存储索引时,这条规则可以“忽视”,因为即使表的行数少于102,400,也可以形成能够转接为列存储的行组。

其实简单想想也能理解,因为索引创建时,SQL Server不可能等着表着行数增大到102,400后再去形成列存储。

在下面我演示了一个示例,表MyUser1有102行数据,我在表上创建了一个列存储索引,大家可以看到只有一个包含了102行的行组,且该行组已经转换为列存储了。

SELECT COUNT(*)AS rows_count FROM myuser1

SELECT i.object_id, object_name(i.object_id) ASTableName, i.nameASIndexName,

CSRowGroups.state_description,CSRowGroups.row_group_id,CSRowGroups.total_rows, CSRowGroups.*,100*(total_rows - ISNULL(deleted_rows,0))/total_rows ASPercentFullFROM sys.indexes ASiJOIN sys.column_store_row_groups ASCSRowGroups

ON i.object_id = CSRowGroups.object_id

AND i.index_id =CSRowGroups.index_id--WHERE object_name(i.object_id) = ''

ORDER BY object_name(i.object_id), i.name, CSRowGroups.row_group_id;

图中,该表只有一个行组,行组ID为0(row_group_id),因为state_description为compressed(下文会详细介绍state_description的值所代表的意义),说明该行组已经按照列方式存储了。

②:将行组按列划分列块;

行组在达到指定的大小后(102400-1048576),必须按照列进行拆分,每一列形成一个列块。每个列块包含了这一列的所有数据。

再来看我的示例,表MyUser1一共有33列,按照列块的定义,就会有33个列块。

SELECT max_column_id_used FROM SYS.TABles WHERE OBJECT_ID=OBJECT_ID('MYUSER1')

SELECT i.name, p.object_id, p.index_id, i.type_desc,COUNT(*) ASnumber_of_segmentsFROM sys.column_store_segments ASs

INNER JOIN sys.partitions ASpON s.hobt_id =p.hobt_idINNER JOIN sys.indexes ASi

ON p.object_id = i.object_id

GROUP BY i.name, p.object_id, p.index_id,

i.type_desc ;GO

图示:myuser1表的列数33。

图示:myuser1表的列块数33(number_of_segments)。

③:每个列块被压缩后存储在物理磁盘上;

步骤2中列块的形成不是目的,只是手段。

列块必须压缩后才能真正按照列方式存储,根据MSDN说法,压缩后形成的列存储最大可以节省7倍的磁盘空间,

在如下示例中,我做了两个表,simpletable上有列聚集索引,simpletable_nocci上没有列聚集索引,只有聚集索引,每张表都包含了1048577行数据,且数据内容完全相同。

我们通过sp_spaceused来查看下两个表的磁盘空间占用情况,

sp_spaceused 'simpletable_nocci'

GOsp_spaceused

'simpletable'

GO

显然,simpletable_nocci的磁盘空间占用亮是simpletable的2倍多。

INSERT和BULK INSERT

在含有列聚集索引的表插入的行需要经过行——行组——列块——列存储的过程。这与传统表中插入数据是不同的。。

下面我们通过两种SQL Server insert方法来了解列存储中插入数据的过程。

(1) INSERT

Insert,也称为TRICKLE  Insert,我们通常使用的insert into就是TRICKLE  Insert。

在SQL 2014中,每次insert的行不会直接写入到列存储中,

因为这样会产生大量的索引碎片,而且这种零散的插入不能获得很好的压缩效果,影响列存储和查询的性能。

对于这些新插入的数据,SQL 2014中引入了DELTA STORE临时表,

新些插入的行还是按照行的方式存储在DELTA STORE中,并可以通过B-Tree进行检索。也就说,DELTA STORE其实传统表的存储结构一样,也是行式存储。

如图:

当DELTA STORE中行组的行数达到所要求的1048,576行时,该行组就会被标记为CLSOED,不再允许新数据插入。

然后SQL 2014的后台进程Move Truple扫描到CLSOED的行组时,会将该行组从delta store迁到列存储,最后将该行组标记为COMPRESSED。

如图:

RowGroup1的Row已经达到行组的最大值,该行组被标记为CLOSED,表示不能在接受新数据插入。

RowGroup2的Row小于1048576行,即使满足行组要求的最小值,也还是在OPEN状态,直到达到行组最大大小。

行组的状态可以通过如下语句查询得到:

SELECT i.object_id, object_name(i.object_id) ASTableName, CSRowGroups.state_descriptionFROM sys.indexes ASiJOIN sys.column_store_row_groups ASCSRowGroupsON i.object_id = CSRowGroups.object_id

AND i.index_id =CSRowGroups.index_id--WHERE object_name(i.object_id) = ''

ORDER BY object_name(i.object_id), i.name, row_group_id;

(2)BULK INSERT

Bulk insert可以理解为一种高性能的插入方法,Bulk Insert常常用于大数据导入操作,其性能要比Trickle  Insert好很多,

(有兴趣的读者可以自己验证下,插入相同行的数据,哪个更快一点。)

也正是基于此,Bulk Insert在列存储中插入数据的方法与和Trickle  Insert也有些区别。

当一次Bulk Insert的数据达到行组的最小值102,400时,该行组可以不经过delta Store而直接按照列方式存储。

这里需要引起我们注意的是,之前我们说Trickle  Insert的列存储形成过程,不仅要求行组达到1048576,而且还必须先存储在delta Store中。

由此可见在大批量的数据导入中,Bulk insert是首选的方法。

下面的实例中,我将一个含有102,400的t1.txt通过bulk insert导入到数据库中,可以看到该行组的结果直接变为COMPRESSED。

bulk insertBULK INSERT simpletable FROM 'd:\temp\t1.txt'

t1.txt的格式如下:

不过如果一次Bulk Insert插入的数据大于一个行组但小于两个行组时,多出的这部分数据必须也存储在delta Store中。

DELETE和UPDATE

因为列聚集索引的表可能同时包含行存储的delta store和列存储,所以在处理删除和更新时,两个不同区域会有所不同。

(1)delete

我们先看删除操作:

如果删除的行在列存储中,SQL Server只是从逻辑上删除它,其占用的物理空间并不会释放

SQL Server 2014 的delete bitmap是用来跟踪列存储中的每一个记录删除情况的表,它跟delta store一样也是基于行和B-tree的方式存储。

当某一行需要被删除时,delete bitmap会将该行对应的bit标记为删除状态,实际上行的所在的物理区域没有发生变化。

这样就要求所有的查询语句必须先扫描delete bitmap,对于哪些已被记为删除的记录就不要到物理存储查找了,也不需要出现在查询的结果中。

如果删除的行在Delta Store中,这跟传统行存储的删除方式没有区别,SQL Server会从逻辑上和物理上都删除该数据。

(2)update

理解了insert和delete后,我们再来看update就非常简单了。

如果update发生在列存储中

SQL Server会将该行在delete bitmap中bit标记为删除状态,同时插入一新行到delta store中。

如果update发生在delta store中

SQL Server直接更新delta store中这行的数据。

结论

尽管SQL Server 2014的列存储已经支持数据更新,但并不意味着生产环境下的报表能够从中获益。

列存储天生是为OLAP设计,其数据特征更趋向于静态,即使是数据的导入,微软也建议使用bulk insert,

所以如果数据库的日常操作中存在大量的增、删、改等操作,使用列存储的技术可能会适得其反。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
索引下推(Index Pushdown)是一种数据库查询优化技术,它通过将查询尽可能地转换为索引操作,从而减少了数据的读取和传输量,提高了查询性能。在索引下推中,查询优化器会尝试将查询条件下推到索引层级进行处理,以避免全表扫描或者大量数据的传输和读取。 具体来说,索引下推可以分为以下两种形式: 1. 索引条件下推:将查询条件下推到索引层级进行处理,只读取满足条件的数据,从而减少数据的读取和传输量。例如,对于以下SQL语句: ```sql SELECT * FROM table_name WHERE column_name = 'value'; ``` 如果 `column_name` 上存在索引,查询优化器可以将查询条件下推到索引层级进行处理,只读取满足条件的数据,而不是读取整张表的数据。 2. 投影下推:将查询的下推到索引层级进行处理,只读取需要的数据,避免读取不必要的数据,从而减少数据的传输量。例如,对于以下SQL语句: ```sql SELECT column_name FROM table_name WHERE column_name = 'value'; ``` 如果 `column_name` 上存在索引,查询优化器可以将查询的下推到索引层级进行处理,只读取需要的数据。这样可以避免读取不必要的数据,提高查询性能。 需要注意的是,索引下推并不是所有数据库系统都支持的功能,具体要看数据库系统的实现。在MySQL中,索引下推被称为“索引下推优化”(Index Condition Pushdown,ICP),它可以通过开启 `condition_pushdown_for_derived` 和 `condition_pushdown_for_subquery` 两个参数来启用。在Oracle中,索引下推被称为“表达式下推”(Predicate Pushdown),它可以通过开启 `PUSH_PRED` 优化参数来启用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值