书签能够帮助SQL Server快速从非汇集索引条目导向到对应的行,其实这东西几句话我就能说明白。数据库
若是表有汇集索引(区段结构),那么书签就是从非汇集索引找到汇集索引后,利用汇集索引定位到数据。此处的书签就是汇集索引。若是表没有汇集索引(堆结构)。那么扫描非汇集索引后,经过RID定位到数据,那么此处书签就是RID。ui
所谓的书签查找,就是经过汇集索引,而后利用汇集索引或RID定位到数据。spa
不论表示堆结构仍是区段结构,数据的存放都是数据库文件的某文件->某页->某行,所以定位数据的文件组合起来就是
文件号:页号:行号。这三个数字就是RID。如文件1的第77页的第12行的RID就是1:77:12。指针
堆结构与区段结构不一样,一般堆上的行不会改变位置,一旦他们被插入某个页中,他们就会一直在那个位置。在堆上的行不多移动,若是行被移动的话,他们会在原来的位置留下指向其移动到的新位置的指针。而区段结构的行,是能够移动的,在添加数据或整理索引时,均可以会被移动位置。code
由于在堆上的行不多移动,因此RID就能够惟一标识某一行,RID的值不只仅不变,RID所表示的行的物理位置也不会变,这使得RID的值更适宜做为书签。这也是为何SQL Server在堆上创建的非汇集索引的书签都使用RID。blog
一、堆上的非汇集索引:基于RID的书签索引
CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate --主键不是汇集索引,没有汇集索引 ON Sales.SalesOrderDetail(ProductID, ModifiedDate) INCLUDE (OrderQty, UnitPrice, LineTotal)
部分数据顺序:内存
注意到以上数据是无序的。it
上面创建的非汇集索引由于使用了RID做为书签,直接指向对应行所在的物理位置,所以效率不错。虽然RID值用于键查找很是高效,但书签中包含的值与具体的用户数据无关。ast
二、在汇集索引上的非汇集索引:基于汇集键的书签
若是表示基于汇集索引的,则表内数据能够在表移动。所以,对于汇集索引来讲,RID并不能一直不变的定位一个相同的行。所以必须用另外的方法定位行,这个方法就是使用汇集索引的索引键。
使用汇集索引键做为书签可使得当数据在页中的行改变时,不须要非汇集索引的书签的值进行变更,所以非汇集索引的键就能够用于去找底层表的数据,即根据书签取数据再也不基于物理位置,而是基于汇集索引查找。
以汇集索引键做为非汇集索引的书签最好要汇集索引键知足以下标准:
索引应该具备惟一性:每个索引条目书签都应该使得书签能够经过汇集索引的键值惟一的确认表中的一行,若是你建立的汇集索引键值不惟一,SQL Server将会为有重复键值的每一行自动加上一个叫uniquifier的东西使得每一行惟一。这个uniquifier对客户端是透明的。对因而否能够容许汇集索引键重复,要考虑如下两点:
索引键应该短:索引键所占的字节数应该短.由于这个键还会占用非汇集索引书签的空间。好比Contact表中以Last name / first name / middle name / street组合做为索引键看上去不错,但若是表中存在多个非汇集索引的话状况就有些微妙了。n个非汇集索引使得Last name / first name / middle name / street这些字段被存储在n+1个位置。
索引键最好不要变更:也就是索引键的值最好不要变更。对于汇集索引键的修改会使得基于这个汇集索引的全部非汇集索引一样进行修改。因此对于汇集索引的一次update会形成n个非汇集索引书签的update+1个汇集索引键值自己的update。
下面以一个示例来帮助理解书签查找:
假设数据库有一张表以下:
咱们再Name列建一个非汇集索引,而后执行下面的语句:
从执行计划咱们能够看到,由于Age列并不在非汇集索引中,因此SQL Server经过“键查找”引导到汇集表获取数据,这就是书签查找。
书签查找的目的,就是为了从非汇集索引导航到基本表获取非汇集索引中并未包含的信息。
书签查找要求访问索引页面以外的数据页面,访问两组页面增长了查询逻辑读操做次数。并且,若是页面不在内存中,书签查找可能须要在磁盘上一个随机I/O操做来从索引页面跳转到数据页面,还须要必要的CPU能力来聚集这一数据并执行必要的操做。这是由于对于大的表,索引页面和对应的数据页面一般在磁盘上并不临近。
若是须要增长逻辑读操做或者开销较大的物理读操做使书签查找的数据检索操做开销至关大,这个开销因素是非汇集索引更适合于返回较小的数据行数的缘由。随着查询检索的行数增长,书签查找的开销将变得没法接受。
为了理解书签查找随着检索行数增长而使feu汇集索引无效,下面来看一个实例:
仍是那张Person表,一万数据。此次,我把索引建在Id列,Id列的惟一性是1,由于原来Id列是作主键+汇集索引的,但被我删掉了。
咱们来看看下面两个查询的执行计划,
返回100条:
返回300条:
咱们看到,当要求返回300条数据的时候,SQL Server就不在使用Id列上的非汇集索引,而是直接进行表扫描了。由于SQL Server认为执行300次书签查找还不如直接对一张1万条记录的表进行全表扫描。
由上面的实例能够得出结论,返回大的结果集将增长书签查找的开销,甚至低于表扫描。所以在返回较大结果集的状况下,必须考虑避免书签查找的可能性。
书签查找多是一个开销较大的操做,因此应该分析查询计划,在执行计划中选择一个关键字查找步骤的缘由。可能发现能够经过在非汇集索引键中包含丢失的行,或者做为索引页面级别上的包含列来避免书签查找,从而避免与书签查找相关的开销。
从上面的实例,咱们能够提出观点:若是查询的各部分(不仅是选择列表)中引用的列不都包含在使用的非汇集索引中,就会发生书签查找操做。
下面介绍一个技巧,咱们点击某一个执行计划的图标以后,就能在右侧的属性信息栏里获取到相关的执行信息。例如,输出列表就是本执行计划的要返回的列。
由于书签查找的相对开销可能很是高,因此应该尽量尝试摆脱书签查找操做。下面给出一下方案。
一、使用汇集索引
对于汇集索引,索引的叶子页面和表的数据页面相同。所以,当读取汇集索引键列的值时,数据引擎能够读取其余列的值而不须要任何导航。例如前面的区间数据查询的操做,SQL Server经过B树结构进行查找是很是快速的。
把非汇集索引转换为一个汇集索引提及来很简单。可是,这个例子和大部分可能遇到的状况下,这不可能作到,由于表已经有了一个汇集索引。这个表的汇集索引刚好是主键。必须卸载掉全部的外键约束,卸载而且重建为一个非汇集索引。这不只要考虑所涉及的工做,还可能严重地影响依赖于现有汇集索引的其余查询。
二、使用覆盖索引
为了理解覆盖索引是如何避免书签查找,咱们仍是对于Person来执行以下两个查询:
下面修改索引增长Name列。
因为非汇集索引上已经有了须要查询的Id和Name列的数据,因此不在须要书签查找定位到基本表。
三、使用索引链接
若是覆盖索引变得很是宽,那么可能要考虑索引链接技术。索引链接技术使用两个或更多索引之间的一个索引交叉来彻底覆盖一个查询。由于索引链接技术须要访问多余一个索引,它必须在全部索引链接中使用的索引上执行逻辑读。所以,索引链接须要比覆盖索引更高的逻辑读数量。可是,由于索引链接所用的多个窄索引可以比宽的覆盖索引服务更多的查询。因此索引链接也能够做为避免书签查找的一种技术来考虑。
咱们来看下面的实例:
留意到,上面的例子咱们建立了两个非汇集索引,一个在 Id列,一个在Name列。可是咱们的查询须要同时返回Id列和Name列。而这两个非汇集索引都不彻底包含要返回列。这个时候,哈希匹配目的就是经过定位到索引,而不用定位到基本表就可以得到咱们所须要的所有数据,这样索引链接就避免了书签查找。