【译注:此文为翻译,由于本人水平所限,疏漏在所难免,欢迎探讨指正】 

原文链接:传送门

在之前的章节,我们把索引看做一组有序条目的集合,每行数据对应一个索引条目。我们解释了很多关于索引逻辑方面的内容,集中在各个条目的数据上以及把它们包含进索引建序列所产生的影响上。因此到目前为止,我们已经涉及了非聚集索引条目的前两个组件:检索键(search key)和包含列(included columns)。本章节我们将检查它的第三个,也是最后一个组件:书签(bookmark)。

书签里是什么?

      之前我们提到过书签,但仅仅是说它使得SQL SERVER从非聚集索引条目快速导航到所交互的行数据上。现在是时候深入书签的更多细节了,毕竟,书签的内容根据其底层表是堆或者是聚集索引而有所不同。

不管表是一个堆或者是一个聚集索引,表中的每一行数据都是其所在页的第N行,所在页都是其所在数据文件的第N个页,那个文件总是组成数据库的文件组的第N个文件。因此,数据库的每一行数据,在任何给定的时刻,都能被三个数字来标识:文件号,页号,行号。这种由三个数字组成的标识称为 行ID(row id),通常简写为RID。显示SQL SERVER内部信息的大部分工具都会以冒号分割的形式来显示这三个数字,因此1号文件的77页的12行数据将会有一个这样的RID: 1:77:12。

一般而言,堆的数据行不会移动,一旦它们被插入进一个页中,便会一直在那个页中。从技术角度更精确的说,堆中的数据行很少移动,当它们确实发生移动时,便会在原来的位置留下跳转地址。然而,聚集索引的数据行可以移动,也就是说,在数据更新或者索引重组时,它们能被重新定位到另一个页。关于数据行被更新时会发生什么的更多细节(包含关于跳转行的信息)将在后续章节给予介绍。

既然堆的数据行不会移动,那么RID 便可以永久的标识堆中的数据行,这个值不仅是恒定的,它也实际确定了行的位置,这使得它成为了在堆的非聚集索引中表示书签值的理想值,这便是SQL SERVER在堆上创建的非聚集索引中使用它(RID)的原因。

堆的非聚集索引:基于RID的书签

     我们说表SalesOrderDetail 是一个堆,它的数据行没有特定的顺序,然后我们创建了在第五节使用的索引:在ProductID / ModifiedDate列建立的非聚集索引,并包含了包含列,如下所示:

CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate
ON Sales.SalesOrderDetail(ProductID, ModifiedDate)
INCLUDE (OrderQty, UnitPrice, LineTotal)

在这个非聚集索引中,按序排列的条目看起来像是这样:

:- Key Columns -:      :---  Included Columns  ---:          : Bookmark :

ProductID   ModifiedDate      OrderQty UnitPrice LineTotal
----------- ------------ -------- --------- --------- ------------- Page n-1: 709 01 Feb 2002 1 5.70 5.70 3:9198:41
709 01 May 2002 1 5.70 5.70 3:969:2
710 01 Jul 2001 1 5.70 5.70 3:9840:29
710 01 Jul 2001 1 5.70 5.70 3:9916:29
710 01 Sep 2001 1 5.70 5.70 4:12331:32 Page n: 710 01 Oct 2001 1 5.70 5.70 3:1911:33
710 01 Nov 2001 1 5.70 5.70 4:2604:34
710 01 Nov 2001 1 5.70 5.70 4:2889:34
710 01 Nov 2001 1 5.70 5.70 3:3522:35
710 01 Nov 2001 1 5.70 5.70 3:3623:35 710 01 Jun 2002 1 5.70 5.70 4:3917:5
712 01 Jul 2001 1 5.19 5.19 3:9886:29
712 01 Jul 2001 1 5.19 5.19 3:10270:29
712 01 Aug 2001 1 5.19 5.19 4:10609:30
712 01 Aug 2001 1 5.19 5.19 4:10617:30 Page n+1: 712 01 Aug 2001 1 5.19 5.19 4:10689:30
712 01 Aug 2001 1 5.19 5.19 4:10885:30
712 01 Aug 2001 1 5.19 5.19 4:11002:30
712 01 Sep 2001 1 5.19 5.19 4:12318:32
712 01 Sep 2001 1 5.19 5.19 4:509:32

我们的索引行的书签值非常高效,直接指向交互的数据行,但是它们取决于底层的表是一个堆,尽管这些值对于行查找来说非常高效,但是它们没有包含对于用户来说有价值的信息。

这种基于RID的书签的替代方案便是在聚集索引上创建非聚集索引时会用到的书签。简洁精确的说,便是聚集索引表的非聚集索引所使用的书签。

聚集索引的非聚集索引:基于键值的书签

      如果表是一个聚集索引,那么数据行便能在表中重新定位,因此,对于聚集索引来说,RID不能永久标识一个数据行,因而一个不同的值必须被用作此非聚集索引的书签值,这个被用到的值便是聚集索引的索引键。

这解决了需要不变的书签值的需求。因为当聚集索引的一个数据行被移动至新页时,它仅仅被移动,并没有被更新,聚集索引键值便没有变化,因此,此书签值总是可以返回与之交互的行,这就是说数据行会通过索引键返回,而不是通过其实际的物理位置返回。

然而,聚集索引键用作非聚集索引的书签值这种用法意味着聚集索引应该满足三个条件:

 聚集索引必须是唯一的。每一个索引条目书签必须允许SQL SERVER找到与此条目交互的一行数据,如果你创建一个不唯一的聚集索引,SQL SERVER将通过生成额外的值来保证聚集索引的唯一性,这个由SQL SERVER生成的用来保证唯一性的值成为 标识符(uniquifier ),其对任何客户端程序是透明的。基于如下原因,你应该仔细考虑是否允许SQL SERVER在聚集索引中产生重复:

  • 生成标识符需要额外的开销,在插入时,SQL SERVER必须决定,是否新插入的数据行的键与已存在的数据行的键产生重复,如果是,那么便产生标识符加到新行上。
  • 标识符是一条无意义的信息,并且被加在表的非聚集索引中,通常给非聚集索引加上有意义的信息会是更好的做法。

    索引应该简短。也就是说它应该包含少量的字节数,因为它将在所有的非聚集索引中进行传播。Contact 表的 Last name / first name / middle name / street address这几个字段作为聚集索引或许看起来像是一个好主意,但是如果一个表存在多个非聚集索引,它便不是一个好主意了。如果一个表包含N个非聚集索引,那么contact表的 last name / first name / middle name / street address 值便会存储在N+1个位置。

索引应该是静态的。也就是说,它的值应该很少变化,对于一个数据行来说,索引键值的改变强迫各个非聚集索引中此行的条目更新为新键值。因此,对于一个包含N个非聚集索引的表来说,聚集索引键的更改会导致N+1个更改(还不包含 N+1个日志文件的插入)。

当AdventureWorks 数据库的设计团队为SalesOrderDetail 表选择聚集索引时,他们坚持了如上三个指导原则。通过选择SalesOrderID / SalesOrderDetailID 作为聚集索引键达到了窄值,静态以及唯一性的要求。通过添加SalesOrderID 作为索引键的最左列,即使SalesOrderDetailID是唯一的,索引也具有一定的聚集性。一个订单的所有行分组在相同的一两个页上。通过设置SalesOrderID / SalesOrderDetailID 作为表的主键以及聚集索引键,他们消除了在SalesOrderDetailID列上建立单独索引的需求。

没有人会要求去看条目7的数据,他们会要求去看订单#47386的所有条目,或者是订单#47386的#7条目,最重要的是,就如我们说过几次,它使得表处在一个能够最好服务于各个程序的顺序。现在,如果我们在聚集索引表上创建了之前的非聚集索引:

CREATE NONCLUSTERED INDEX FK_ProductID_ModifiedDate
ON Sales.SalesOrderDetail(ProductID, ModifiedDate)
INCLUDE (OrderQty, UnitPrice, LineTotal)

那么此非聚集索引看起来会是这样:

:- Key Columns -:      : ---   Included Columns  ---:    :---   Bookmark   ---:

ProductID   ModifiedDate      OrderQty UnitPrice LineTotal      OrderId     DetailId
----------- ------------ -------- --------- --------- ----------- ---------- Page n-1: 709 01 Feb 2002 1 5.70 5.70 45329 6392
709 01 May 2002 1 5.70 5.70 46047 8601
710 01 Jul 2001 1 5.70 5.70 43670 111
710 01 Jul 2001 1 5.70 5.70 43676 152
710 01 Sep 2001 1 5.70 5.70 44075 1448 Page n: 710 01 Oct 2001 1 5.70 5.70 44303 2481
710 01 Nov 2001 1 5.70 5.70 44484 2853
710 01 Nov 2001 1 5.70 5.70 44499 3006
710 01 Nov 2001 1 5.70 5.70 44523 3346
710 01 Nov 2001 1 5.70 5.70 44527 3400 710 01 Jun 2002 1 5.70 5.70 46365 10183
712 01 Jul 2001 1 5.19 5.19 43673 136
712 01 Jul 2001 1 5.19 5.19 43694 342
712 01 Aug 2001 1 5.19 5.19 43846 524
712 01 Aug 2001 1 5.19 5.19 43847 528 Page n-1: 712 01 Aug 2001 1 5.19 5.19 43851 567
712 01 Aug 2001 1 5.19 5.19 43863 672
712 01 Aug 2001 1 5.19 5.19 43871 735
712 01 Sep 2001 1 5.19 5.19 44074 1441
712 01 Sep 2001 1 5.19 5.19 44109 1729

我们的两个非聚集索引版本:之前在堆上创建的版本和在聚集索引上创建的版本,除了书签的内容不同外,其他地方是完全相同的。

哪一个更好?

       这些设计中的一个比其他的更好吗?或许是吧,但是也好不了太多。基于RID 的书签允许在表中对数据行进行快速查找,索引键书签导致了对应底层表的更慢的数据行查找,但增加了索引覆盖查询的可能性,它通常包含了一个用来做JOIN连接的外键值。

“哪个是更好的?”这个问题的真正答案是:“两者都不”。当为一个表创建索引时,最重要的决策是:哪个索引应该成为表的聚集索引?一旦作出决策(根据本章早些时候提到的指导方案),你不必担心那个决策对于非聚集索引的影响,它们将表现良好。

结论

一个非聚集索引条目包含了检索键,包含列,书签。书签值要么是RID,要么是聚集索引键,这决定于底层表是堆还是聚集索引。对于表来说,选择最合适的聚集索引需要你遵守上述三个指导准则,这样可以确保这个索引键会成为良好的书签值。

【译】索引进阶(六):SQL SERVER索引书签的更多相关文章

  1. c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程

    c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...

  2. SQL Server索引总结二

    从CREATE开始 通过显式的CREATE INDEX命令 在创建约束时作为隐含的对象 随约束创建的隐含索引 当向表中添加如下两种约束之一时,就会创建隐含索引. 主键约束(聚集索引) 唯一约束(唯一索 ...

  3. SQL Server索引碎片整理实际操作记录

    SQL Server 版本是 2008 R2. 查询数据库索引碎片情况的 SQL 语句(来源): SELECT OBJECT_NAME(ind.OBJECT_ID) AS TableName, ind ...

  4. 【译】SQL Server索引进阶第八篇:唯一索引

    原文:[译]SQL Server索引进阶第八篇:唯一索引     索引设计是数据库设计中比较重要的一个环节,对数据库的性能其中至关重要的作用,但是索引的设计却又不是那么容易的事情,性能也不是那么轻易就 ...

  5. 【译】索引进阶(一):SQL SERVER索引介绍

      [译注:此文为翻译,由于本人水平所限,疏漏在所难免,欢迎探讨指正]  原文链接:http://www.sqlservercentral.com/articles/Stairway+Series/7 ...

  6. SQL Server索引进阶:第七级,过滤的索引

    原文地址: Stairway to SQL Server Indexes: Level 7,Filtered Indexes 本文是SQL Server索引进阶系列(Stairway to SQL S ...

  7. SQL Server索引进阶:第十级,索引内部结构

    原文地址: Stairway to SQL Server Indexes: Level 10,Index Internal Structure 本文是SQL Server索引进阶系列(Stairway ...

  8. SQL Server索引进阶:第九级,读懂执行计划

    原文地址: Stairway to SQL Server Indexes: Level 9,Reading Query Plans 本文是SQL Server索引进阶系列(Stairway to SQ ...

  9. SQL Server索引进阶:第八级,唯一索引

    原文地址: Stairway to SQL Server Indexes: Level 8,Unique Indexes 本文是SQL Server索引进阶系列(Stairway to SQL Ser ...

  10. SQL Server索引进阶:第六级,标签

    原文地址: Stairway to SQL Server Indexes: Level 6,Bookmarks 本文是SQL Server索引进阶系列(Stairway to SQL Server I ...

随机推荐

  1. 2018-2019-2 20175228实验一《Java开发环境的熟悉》实验报告

    一.实验内容及步骤 (一)使用JDk编译.运行简单的Java程序 实验步骤如下: 实验截图如下: (二)使用IDEA调试程序 1.设置断点2.单步运行:Step Into(快捷捷F7)和Step Ov ...

  2. 0.[Andriod]之从零安装配置Android Studio并编写第一个Android App

    0. 所需的安装文件 笔者做了几年WP,近来对Android有点兴趣,尝试一下Android开发,废话不多说,直接进入主题,先安装开发环境,笔者的系统环境为windows8.1&x64. 安装 ...

  3. WebApi的版本控制

      using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; using Sy ...

  4. LOJ2514 CEOI2011 Hotel 贪心

    传送门 考虑一个贪心:对于所有人群按照收益从大到小排序,对于每一个人群找到当前能够选择的代价最小的房间成为一组可行订单(如果没有就不可行),最后将这些订单按照收益排序,选其中正的前\(o\)大即可.找 ...

  5. Educational Codeforces Round 62 (Rated for Div. 2) Solution

    最近省队前联考被杭二成七南外什么的吊锤得布星,拿一场Div. 2恢复信心 然后Div.2 Rk3.Div. 1+Div. 2 Rk9,rating大涨200引起舒适 现在的Div. 2都怎么了,最难题 ...

  6. left join inner join 区别

    left 以左表为准,左表在右表没有对应的记录,也为显示(右表字段填空). inner 需要满足两张表都有记录. 不管哪种join 一对多最终的结局 只会是多条记录

  7. .net 信息采集ajax数据

    .net 信息采集ajax数据 关于.net信息采集的资料很多,但是如果采集的网站是ajax异步加载数据的模式,又如何采集呢?今天就把自己做信息采集时,所遇到的一些问题和心得跟大家分享一下. 采集网站 ...

  8. Label Encoding vs One Hot Encoding

    最近在刷kaggle的时候碰到了两种处理类别型特征的方法:label encoding和one hot encoding.我从stackexchange, quora等网上搜索了相关的问题,总结如下. ...

  9. 你不知道的 requestIdleCallback

    本文副标题是 Request Schedule 源码解析一.在本章中会介绍 requestIdleCallback 的用法以及其缺陷, 接着对 React 团队对该 api 的 hack 部分的源码进 ...

  10. windows下安装和使用scrapy

    首先,要确保已经正确安装了python环境,并安装了pip包 接着,打开cmd或者powershell ,输入命令 pip install scrapy .安装完之后 运行scrapy性能测试命令: ...