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

原文链接:传送门

在之前的章节,我们把索引看做一组有序条目的集合,每行数据对应一个索引条目。我们解释了很多关于索引逻辑方面的内容,集中在各个条目的数据上以及把它们包含进索引建序列所产生的影响上。因此到目前为止,我们已经涉及了非聚集索引条目的前两个组件:检索键(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. BZOJ4025 二分图 线段树分治、带权并查集

    传送门 如果边不会消失,那么显然可以带权并查集做(然后发现自己不会写带权并查集) 但是每条边有消失时间.这样每一条边产生贡献的时间对应一段区间,故对时间轴建立线段树,将每一条边扔到线段树对应的点上. ...

  2. vue 项目设置实现通过本地手机访问

    1.查询ip地址 win+R打开cmd 窗口 输入ipconfig 找到IPV4地址   192.168.x.xxx 2.在vue项目config文件夹中的index.js,将localhost换为i ...

  3. supervisor 守护者进程配置小记

    安装 Supervisor 联网状态下,官方推荐首选安装方法是使用easy_install,它是setuptools(Python 包管理工具)的一个功能.所以先执行如下命令安装 setuptools ...

  4. C# Note35: 异步操作

    .NET Framework 为异步操作提供了两种设计模式:使用 IAsyncResult 对象的异步操作与使用事件的异步操作. IAsyncResult 异步设计模式 通过名为 BeginOpera ...

  5. final关键字总结

    参考文章[https://www.cnblogs.com/xrq730/p/4820296.html] 前言:在Java中final作为最常见的一个关键字,平常用到的地方也很多,接下来就来看看什么是f ...

  6. 关于win10企业版在极域电子教室软件 v4.0 2015 豪华版的全屏控制下如何取得自由

    注.可能因为系统和软件的缘故无法实现 背景 由于在听课过程过于自闭,于是想自己去网上搜点东西看下 于是 经过了一番乱搞 逐渐摸索出了现方法. 方案1: 大力出奇迹 由于电脑在刚刚进入的状态的时候有段时 ...

  7. Hack The Box 获取邀请码

    TL DR; 使用curl请求下面的地址 curl -X POST https://www.hackthebox.eu/api/invite/generate {"success" ...

  8. AGC027B Garbage Collector

    一道很好的构造题 原题链接 很快就能想到,捡每个垃圾的能量可以最后再算.然后,对于每个垃圾,在路上耗费的能量仅与它是第几个被捡的有关,于是我们考虑将垃圾分组. 首先,我们定义\(F(x,i)\)为某次 ...

  9. Vue+koa2开发一款全栈小程序(7.图书录入功能)

    1.图书录入功能 1.获取图书信息 1.在mydemo/src/until.js中封装工具函数post和get // 工具函数 import config from './config' // htt ...

  10. elk每日清除30天索引脚本

      日常elk产生日志太多,故写个脚本放在定时任务,定时清理脚本 查询索引: curl -XGET 'http://127.0.0.1:9200/_cat/indices/?v'   删除索引: cu ...