做者:David Durant,2013/01/25(首次发布于:2011/06/22)sql
本文是属于Stairway系列:Stairway to SQL Server Indexes数据库
索引是数据库设计的基础,并告诉开发人员使用数据库关于设计者的意图。不幸的是,当性能问题出现时,索引每每被添加为过后考虑。这里最后是一个简单的系列文章,应该使他们快速地使任何数据库专业人员“快速”数据结构
这个阶段的前面的层次提供了通常索引和非汇集索引的概述。它如下面关于SQL Server索引的关键概念结束。当请求到达您的数据库时,不管是SELECT语句仍是INSERT,UPDATE或DELETE语句,SQL Server都只有三种可能的方式来访问语句中引用的表的数据:数据库设计
这个级别的重点是上面列表中的第三个选项。搜索表。这反过来又会引导咱们讨论汇集索引。在第二级提到但没有涉及的主题。sqlserver
咱们将在此级别使用的主要AdventureWorks数据库表是SalesOrderDetail表。在121,317行,它足以说明在表上有汇集索引的一些好处。并且,有两个外键,足以说明一些关于聚簇索引的设计决策。性能
尽管咱们已经讨论过一级的样本数据库,可是这个时候仍是要重复的。 在整个这个阶段,咱们将用例子来讲明概念。 这些示例基于Microsoft AdventureWorks示例数据库。 咱们专一于销售订单。 五张表将给咱们交易和非交易数据的良好组合; Customer,SalesPerson,Product,SalesOrderHeader和SalesOrderDetail。 为了保持重点,咱们使用列的一个子集。 因为AdventureWorks规范化很好,销售人员信息被分解为三个表:SalesPerson,Employee和Contact。ui
在整个阶梯中,咱们使用如下两个术语来交换订单上的单行:“订单项”和“订单明细”。 前者是更常见的业务术语; 后者出如今AdventureWorks表的名字内。spa
图1显示了一整套表格及其之间的关系。设计
图1:这个Stairway的例子中使用的表code
注意:
在这个楼梯级别显示的全部TSQL代码能够与文章一块儿下载。
咱们首先提出如下问题:若是不使用非汇集索引,须要多少工做才能在表中找到一行?在表中搜索请求的行意味着扫描无序表中的每一行吗?或者,SQL Server能够永久性地对表中的行进行排序,以便经过搜索关键字快速访问它们,就像经过搜索关键字快速访问非汇集索引的条目同样?答案取决于您是否指示SQL Server在表上建立聚簇索引。
与非聚簇索引是一个独立的对象并占用他们本身的空间不一样,聚簇索引和表是同样的。经过建立汇集索引,能够指示SQL Server将表中的行排序为索引键序列,并在未来的数据修改期间维护该序列。即将到来的级别将查看生成的内部数据结构来完成此操做。但如今,把聚簇索引看做是一个有序表。给定一个行的索引键值,SQL Server能够快速访问该行;并能够从该行按顺序进行。
为了演示目的,咱们建立了示例表SalesOrderDetail的两个副本;一个没有索引,一个有汇集索引。关于索引的关键字段,咱们作出与AdventureWorks数据库的设计者作出相同的选择:SalesOrderID / SalesOrderDetailID。清单1中的代码建立了SalesOrderDetail表的副本。咱们能够随时从新运行这个代码,咱们但愿从一个“干净的石板”开始。
IF EXISTS (SELECT * FROM sys.tables  WHERE OBJECT_ID = OBJECT_ID('dbo.SalesOrderDetail_index')) DROP TABLE dbo.SalesOrderDetail_index; GO IF EXISTS (SELECT * FROM sys.tables  WHERE OBJECT_ID = OBJECT_ID('dbo.SalesOrderDetail_noindex')) DROP TABLE dbo.SalesOrderDetail_noindex; GO SELECT * INTO dbo.SalesOrderDetail_index FROM Sales.SalesOrderDetail; SELECT * INTO dbo.SalesOrderDetail_noindex FROM Sales.SalesOrderDetail; GO CREATE CLUSTERED INDEX IX_SalesOrderDetail ON dbo.SalesOrderDetail_index (SalesOrderID, SalesOrderDetailID) GO
清单1:建立SalesOrderDetail表的副本
所以,在建立汇集索引以前,假设SalesOrderDetail表以下所示:
SalesOrderID SalesOrderDetailID ProductID OrderQty UnitPrice 69389 102201 864 3 38.10 56658 59519 711 1 34.99 59044 70000 956 2 1430.442 48299 22652 853 4 44.994 50218 31427 854 8 44.994 53713 50716 711 1 34.99 50299 32777 739 1 744.2727 45321 6303 775 6 2024.994 72644 115325 873 1 2.29 48306 22705 824 4 141.615 69134 101554 876 1 120.00 48361 23556 760 3 469.794 53605 50098 888 1 602.346 48317 22901 722 1 183.9382 66430 93291 872 1 8.99 65281 90265 889 2 602.346 52248 43812 871 1 9.99 47978 20189 794 2 1308.9375
在建立如上所示的汇集索引以后,生成的表/汇集索引将以下所示:
SalesOrderID SalesOrderDetailID ProductID OrderQty UnitPrice 43668 106 722 3 178.58 43668 107 708 1 20.19 43668 108 733 3 356.90 43668 109 763 3 419.46 43669 110 747 1 714.70 43670 111 710 1 5.70 43670 112 709 2 5.70 43670 113 773 2 2,039.99 43670 114 776 1 2,024.99 43671 115 753 1 2,146.96 43671 116 714 2 28.84 43671 117 756 1 874.79 43671 118 768 2 419.46 43671 119 732 2 356.90 43671 120 763 2 419.46 43671 121 755 2 874.79 43671 122 764 2 419.46 43671 123 716 1 28.84 43671 124 711 1 20.19 43671 125 708 1 20.19 43672 126 709 6 5.70 43672 127 776 2 2,024.99 43672 128 774 1 2,039.99 43673 129 754 1 874.79 43673 130 715 3 28.84 43673 131 729 1 183.94
当您查看上面显示的示例数据时,您可能会注意到每一个SalesOrderDetailID值都是惟一的。 不要混淆; SalesOrderDetailID不是表的主键。 SalesOrderID / SalesOrderDetailID的组合是表的主键; 以及聚簇索引的索引键。
聚簇索引键能够由您选择的任何列组成; 它没必要以主键为基础。 在咱们的例子中,最重要的是最左边的一列是一个外键,即SalesOrderID值。 所以,销售订单的全部行项目都会在SalesOrderDetail表中连续出现。
请记住如下有关SQL Server聚簇索引的附加要点:
每一个表最多能够有一个聚簇索引。表格的行只能是一个序列。你须要决定什么样的顺序,若是有的话,对每一个表最好;并在可能的状况下在表格填充数据以前建立汇集索引。在作出这个决定时,要记住排序不只意味着排序,并且意味着分组;如按销售订单对订单项进行分组。
这就是为何AdventureWorks数据库的设计者选择SalesOrderID内的SalesOrderDetailID做为SalesOrderDetail表的顺序的缘由;这是订单项的天然顺序。
例如,若是用户请求订单的订单项,则一般会请求该订单的全部订单项。一个典型的销售订单表单告诉咱们,订单的印刷版本老是包含全部的行项目。销售订单业务的性质是按销售订单对行项目进行分组。仓库偶尔会要求按产品而不是销售订单查看订单项,但大部分的要求;如销售人员或客户,打印发票的程序或计算每一个订单总价值的查询;将须要全部销售订单的全部行项目。
然而,用户需求自己并不能决定什么是最好的汇集索引。本系列的将来级别将覆盖指标的内部;由于索引的某些内部方面也会影响你对聚簇索引列的选择。
若是表中没有汇集索引,则该表称为堆。 每一个表都是堆或汇集索引。 因此,虽然咱们常常说每个指标都属于聚类或非聚类两种类型之一, 一样重要的是要注意,每张桌子都属于两种类型之一; 它是一个汇集索引或它是一堆。 开发人员常常说,一个表“有”或“没有”汇集索引,但更有意义的说,表“是”或“不是”汇集索引。
SQL Server在查找行(不包括使用非聚簇索引)时搜索堆只有一种方法,即从表的第一行开始,直到全部行都被读取。 没有序列,没有搜索键,也没法快速导航到特定的行。
为了评估聚簇索引与堆的性能,清单1建立了SalesOrderDetailtable的两个副本。 一个副本是堆版本,另外一个是建立原始表(SalesOrderID,SalesOrderDetailID)上的同一个汇集索引。 这两个表都没有任何非汇集索引。
咱们将对每一个版本的表执行相同的三个查询; 一个检索单个行,一个检索单个订单的全部行,一个检索单个产品的全部行。 咱们在下面的表格中给出了SQL和每一个执行的结果。
咱们的第一个查询检索单个行,执行细节显示在表1中。
SQL | SELECT * FROM SalesOrderDetail WHERE SalesOrderID = 43671 AND SalesOrderDetailID = 120 |
---|---|
Heap | (1 row(s) affected) Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 1495. |
Clustered Index | (1 row(s) affected) Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 3. |
Impact of having the Clustered Index | IO reduced from 1495 reads to 3 reads. |
Comments | No surprise. Table scanning 121,317 rows to find just one is not very efficient. |
表1:检索单行
咱们的第二个查询检索单个销售订单的全部行,您能够在表2中看到执行的详细信息。
SQL | SELECT * FROM SalesOrderDetail WHERE SalesOrderID = 43671 |
---|---|
Heap | (11 row(s) affected) Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 1495. |
Clustered Index | (11 row(s) affected) Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 3. |
Impact of having the Clustered Index | IO reduced from 1495 reads to 3 reads. |
Comments | Same statistics as the previous query. The heap still required a table scan, while the clustered index grouped the 11 detail rows of the requested order sufficiently close together so that the IO required to retrieve 11 rows was the same as the IO required to retrieve one row. An upcoming Level will explain in detail why no additional reads were required to retrieve the additional 10 rows. |
表2:检索单个SalesOrder的全部行
咱们的第三个查询检索单个产品的全部行,执行结果如表3所示。
SQL | SELECT * FROM SalesOrderDetail WHERE ProductID = 755 |
---|---|
Heap | (228 row(s) affected) Table 'SalesOrderDetail_noindex'. Scan count 1, logical reads 1495. |
Clustered Index | (228 row(s) affected) Table 'SalesOrderDetail_index'. Scan count 1, logical reads 1513. |
Impact of having the Clustered Index | IO slightly greater for the clustered index version; 1513 reads versus 1495 reads. |
Comments | Without a nonclustered index on the ProductID column to help find the rows for a single Product, both versions had to be scanned. Because of the overhead of having a clustered index, the clustered index version is the slightly larger table; therefore scanning it required a few more reads than scanning the heap. |
表3:检索单个产品的全部行
前两个查询大大受益于聚簇索引的存在; 第三个是大体相等的。 有时汇集索引是有害的吗? 答案是确定的,主要与插入,更新和删除行有关。 像在这些早期阶段遇到的索引的不少其余方面同样,这也是一个更高级别更详细的主题。
通常来讲,检索效益大于维护损害; 使聚簇索引更适合堆。 若是您要在Azure数据库中建立表,则别无选择。 每一个表都必须是聚簇索引。
聚簇索引是一个有序表,其顺序由您在建立索引时指定,并由SQL Server维护。 根据其关键值,该表中的任何行均可以快速访问。 在索引键序列中,任何一组行均可以经过键的范围快速访问。
每一个表只能有一个聚簇索引。 哪些列应该是聚簇索引键列的决定是您将为任何表格作出的最重要的索引决定。
在咱们的四级中,咱们将重点从逻辑转向物理,介绍页面和范围,并检查索引的物理结构。