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

23 sql-server-2005 sql-server-2008 sql-server

我正在阅读ClusteredNon Clustered Indexes

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

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

查询- 我如何借助实际示例来检查性能差异,因为我们知道该表只能有一个Clustered Index并且提供sortingatClustered Index ColumnNon Clustered Index不提供sorting并且可以支持 999 Non Clustered IndexesinSQL Server 2008和 249 in SQL Server 2005

Nam*_*ian 44

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

首先,当您看到聚集索引时会想到 table。在 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]
Run Code Online (Sandbox Code Playgroud)

所以这里我们有一个基本表,在 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]
Run Code Online (Sandbox Code Playgroud)

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

没错,如果我们执行以下查询:

SELECT * FROM Customer WHERE CustomerID = 1 
Run Code Online (Sandbox Code Playgroud)

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

在此处输入图片说明

所以seek操作的操作次数或O Notation如下:

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

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

SELECT * FROM Customer WHERE CustomerName ='John'
Run Code Online (Sandbox Code Playgroud)

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'
Run Code Online (Sandbox Code Playgroud)

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

在此处输入图片说明

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

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

希望这能解决问题。如果有任何不合理的地方,请发表评论,我会尽力澄清。现在已经很晚了,我的大脑感觉有点平淡。是时候来一头红牛了。


Cad*_*oux 9

“这意味着查询引擎必须采取额外的步骤来定位实际数据。”

不一定 - 如果索引覆盖给定查询,则不必访问数据页。此外,对于包含的列,可以将额外的列添加到非聚集索引中,使其覆盖而不改变键大小。

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

我的一般经验法则是,表始终具有聚集索引(通常在标识或顺序 GUID 上),但为了性能而添加非聚集索引。但总有例外——堆表有一席之地,更广泛的聚集索引也有一席之地。看似冗余的索引更窄以适合每页更多的行。等等等等

而且我不会担心允许的各种索引的限制——这几乎肯定不会在许多现实世界的例子中发挥作用。

  • +1 表示“总是有例外”- 太多人忽略了这一点,并认为无论如何,每个聚集索引都应该是“int 身份”。 (2认同)