聚集索引和非聚集索引之间的性能差异


22

我正在阅读ClusteredNon Clustered Indexes

Clustered Index-它包含数据页。这意味着完整的行信息将出现在“聚簇索引”列中。

Non Clustered Index-它仅包含“聚簇索引”列(如果可用)或“文件标识符+页码+页面中的总行数”形式的行定位器信息。这意味着查询引擎必须采取其他步骤才能找到实际数据。

查询 -如何检查的性能差异与实际例子的帮助,因为我们知道,表只能有一个Clustered Index,并且提供sortingClustered Index ColumnNon Clustered Index不提供sorting,可支持999 Non Clustered IndexesSQL Server 2008,并在249 SQL Server 2005


2
当您做什么时的性能差异?要对该表进行什么样的工作?没有一种解决方案
可以满足

2
这里也许有一些切实的讨论。stackoverflow.com/questions/91688/... stackoverflow.com/questions/5070529/... stackoverflow.com/questions/1251636/...我们可以写一本关于群集和非聚集索引之间的差异论文,但我不认为我们会说出尚不存在的任何内容供您阅读。
阿龙贝特朗

4
您写道:“这意味着查询引擎必须采取其他步骤才能找到实际数据。” 其实,如果你需要的是列涵盖的指数,你不要你在非聚集索引找到你的目标行后需要采取任何额外的步骤。只有当您需要非聚集索引未涵盖的列时,SQL Server才需要执行书签查找
Nick Chammas 2012年

Answers:


43

很好的问题,因为它是一个如此重要的概念。不过,这是一个大话题,我将向您展示的是一个简化,以便您可以理解基本概念。

首先,当您看到聚簇索引思维表时。在SQL Server中,如果表不包含聚集索引,则它是堆。在表上创建聚簇索引实际上会将表转换为b树型结构。您的聚集索引就是您的表,它与表不是分开的

有没有想过为什么您只能有一个聚集索引?好吧,如果我们有两个聚集索引,我们将需要该表的两个副本。它毕竟包含数据。

我将尝试通过一个简单的例子来解释这一点。

注意:我在此示例中创建了表,并在其中填充了超过300万个随机条目。然后运行实际查询并在此处粘贴执行计划。

您真正需要掌握的是O符号操作效率。假设您有下表。

CREATE TABLE [dbo].[Customer](
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[CustomerName] [varchar](100) NOT NULL,
[CustomerSurname] [varchar](100) NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED 
(
[CustomerID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF
  , IGNORE_DUP_KEY = OFF,ALLOW_ROW_LOCKS  = ON
  , ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

因此,这里有一个基本表,在CustomerID上有一个集群键(默认情况下,主键是集群的)。因此,基于主键CustomerID对表进行了排列/排序。中间级别将包含CustomerID值。数据页将包含整行,因此它是表行。

我们还将在CustomerName字段上创建非聚集索引。下面的代码可以做到这一点。

CREATE NONCLUSTERED INDEX [ix_Customer_CustomerName] ON [dbo].[Customer] 
 (
[CustomerName] ASC
 )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF
  , SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF
  , DROP_EXISTING = OFF, ONLINE = OFF
  , ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

因此,在此索引中,您将在数据页/叶级别节点上找到指向聚簇索引中的中间级别的指针。该索引围绕CustomerName字段进行排列/排序。因此,中间级别包含CustomerName值,叶级别包含指针(这些指针值实际上是主键值或CustomerID列)。

正确,所以如果我们执行以下查询:

SELECT * FROM Customer WHERE CustomerID = 1 

SQL可能会通过搜索操作读取聚集索引。查找操作是一种二进制搜索,它比顺序搜索的扫描要有效得多。因此,在上面的示例中,读取了索引,并且通过使用二进制搜索SQL可以消除与我们要查找的条件不匹配的数据。有关查询计划,请参见所附的屏幕截图。

在此处输入图片说明

因此,查找操作的操作数或O表示法如下:

  1. 通过将搜索到的值与中间级别的值进行比较,对聚簇索引执行二进制搜索。
  2. 返回匹配的值(请记住,因为聚集索引中包含所有数据,所以可以返回索引中的所有列,因为它是行数据)

因此,这是两个操作。但是,如果我们执行以下查询:

SELECT * FROM Customer WHERE CustomerName ='John'

现在,SQL将使用CustomerName上的非聚集索引进行搜索。但是,由于这是非聚集索引,因此它不包含该行中的所有数据。

因此,SQL将在中间级别上进行搜索以找到匹配的记录,然后使用返回的值进行查找,以对聚簇索引(又名表)进行另一次搜索以检索实际数据。我知道这听起来令人困惑,但请继续阅读,一切都会变得清楚。

由于我们的非聚集索引仅包含CustomerName字段(存储在中间节点中的索引字段值)和指向作为CustomerID的数据的指针,因此该索引没有CustomerSurname的记录。必须从聚簇索引或表中获取CustomerSurname。

运行此查询时,我得到以下执行计划:

在此处输入图片说明

在上面的屏幕截图中,有两点需要您注意

  1. SQL表示我缺少索引(绿色文本)。SQL建议我在CustomerName上创建一个索引,其中包括CustomerID和CustomerSurname。
  2. 您还将看到查询的99%的时间都花在了对主键索引/聚集索引的键查找上。

为什么SQL再次建议在CustomerName上建立索引?好吧,由于索引仅包含CustomerID,而CustomerName SQL仍必须从表/聚集索引中查找CustomerSurname。

如果创建索引并将索引中包含CustomerSurname列,则SQL只需读取非聚集索引即可满足整个查询。这就是为什么SQL建议我更改非聚集索引的原因。

在这里,您可以看到SQL从集群键中获取CustomerSurname列所需执行的额外操作

因此,操作数如下:

  1. 通过将搜索到的值与中间级别的值进行比较,对非聚集索引执行二进制搜索
  2. 对于匹配的节点,请读取叶级节点,该节点将包含聚集索引中数据的指针(叶级节点将包含主键值)。
  3. 对于返回的每个值,请在聚集索引(表)上进行读取以获取行值,在此我们将读取CustomerSurname。
  4. 返回匹配的行

这是获取值的4个操作。与读取聚簇索引相比,所需的操作量增加了两倍。向您显示,聚集索引是包含所有数据的最强大的索引。

因此,仅澄清最后一点。为什么我说非聚集索引中的指针是主键值?很好地证明了非聚集索引的叶级节点包含主键值,我将查询更改为:

SELECT CustomerID
FROM Customer
WHERE CustomerName='Jane'

在此查询中,SQL可以从非聚集索引读取CustomerID。它不需要对聚集索引进行查找。您可以通过如下所示的执行计划看到。

在此处输入图片说明

注意此查询与上一个查询之间的区别。没有查找。SQL可以在非聚集索引中找到所有数据

希望您能开始理解聚集索引是表,而非聚集索引不包含所有数据。由于可以执行二进制搜索,但只有聚集索引包含所有数据,因此索引将加快选择速度。因此,对非聚集索引的搜索几乎总是会导致从聚集索引中加载值。这些额外的操作使非聚集索引的效率低于聚集索引的效率。

希望这可以清除一切。如果没有任何意义,请发表评论,我将尝试澄清。在这里已经很晚了,我的大脑感觉有点扁平。该换红色公牛了。


我有个问题。为什么在此查询SELECT * FROM Customer WHERE CustomerName ='John'上对CustomerName的非聚集索引进行索引搜索。由于它是非聚集索引,因此不会对客户名进行排序。因此,不应该进行索引扫描。
ckv

顺便说一句,除了上面的问题,答案很清楚。
ckv

1
索引按数据顺序排序。例如,由于它是索引值,因此将根据客户名称进行排序。因此它被排序。请记住,它仍然必须扫描叶子级别或页面。
Namphibian

9

“这意味着查询引擎必须采取其他步骤才能找到实际数据。”

不一定-如果索引涵盖了给定的查询,则无需对数据页进行任何操作。同样,对于包含的列,可以将其他列添加到非聚集索引中,以使其覆盖而不会更改键的大小。

因此,最终的答案是-它取决于(比您在单个问题中所能提供的更多信息)-您需要了解索引的所有功能,并且给定查询的执行计划可能与您的期望有所不同。

我的一般经验法则是,表始终具有聚集索引(通常在身份或顺序GUID上),但是会添加非聚集索引以提高性能。但是总是有例外-堆表有一个地方,更广泛的聚集索引有一个地方。似乎冗余索引的位置较窄,可以容纳每页更多的行。等等等

而且我不会担心所允许的各种索引的限制-几乎可以肯定的是,在许多实际示例中,这不会起作用。


2
+1代表there are always exceptions-太多人忽略了这一点,并认为int identity无论如何,每个聚集索引都应该是。
JNK 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.