23 sql-server-2005 sql-server-2008 sql-server
我正在阅读Clustered和Non Clustered Indexes。
Clustered Index- 它包含数据页。这意味着完整的行信息将出现在聚集索引列中。
Non Clustered Index- 它仅包含聚集索引列(如果可用)或文件标识符 + 页码 + 页面中的总行数形式的行定位器信息。这意味着查询引擎必须采取额外的步骤来定位实际数据。
查询- 我如何借助实际示例来检查性能差异,因为我们知道该表只能有一个Clustered Index并且提供sortingatClustered Index Column和Non 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如下:
所以是两个操作。但是,如果我们执行以下查询:
SELECT * FROM Customer WHERE CustomerName ='John'
Run Code Online (Sandbox Code Playgroud)
SQL 现在将使用 CustomerName 上的非聚集索引来进行搜索。然而,由于这是一个非聚集索引,它不包含行中的所有数据。
因此,SQL 将在中间级别进行搜索以查找匹配的记录,然后使用返回的值进行查找以对聚集索引(也称为表)进行另一次搜索以检索实际数据。我知道这听起来令人困惑,但请继续阅读,一切都会变得清晰。
由于我们的非聚集索引仅包含 CustomerName 字段(存储在中间节点中的索引字段值)和指向 CustomerID 数据的指针,因此该索引没有 CustomerSurname 的记录。必须从聚集索引或表中获取 CustomerSurname。
运行此查询时,我得到以下执行计划:
在上面的屏幕截图中有两件重要的事情需要您注意
为什么 SQL 再次建议 CustomerName 上的索引?好吧,因为索引只包含 CustomerID 并且 CustomerName SQL 仍然必须从表/聚集索引中找到 CustomerSurname。
如果我们创建了索引并且我们在索引中包含了 CustomerSurname 列,SQL 将能够通过读取非聚集索引来满足整个查询。这就是 SQL 建议我更改非聚集索引的原因。
在这里你可以看到 SQL 从聚集键中获取 CustomerSurname 列需要做的额外操作
因此,操作次数如下:
那是 4 个操作来获取值。与读取聚集索引相比,所需的操作量是其两倍。这表明您的聚集索引是最强大的索引,因为它包含所有数据。
所以只是澄清最后一点。为什么我说非聚集索引中的指针是主键值?为了证明非聚集索引的叶级节点包含主键值,我将查询更改为:
SELECT CustomerID
FROM Customer
WHERE CustomerName='Jane'
Run Code Online (Sandbox Code Playgroud)
在这个查询中,SQL 可以从非聚集索引中读取 CustomerID。它不需要对聚集索引进行查找。您可以通过如下所示的执行计划看到这一点。

请注意此查询与上一个查询之间的区别。没有查找。SQL可以找到非聚集索引中的所有数据
希望您可以开始理解聚簇索引是表,非聚簇索引不包含所有数据。由于可以进行二进制搜索,但只有聚集索引包含所有数据,因此索引将加快选择速度。因此,对非聚集索引的搜索几乎总是会导致从聚集索引加载值。这些额外的操作使得非聚集索引的效率低于聚集索引。
希望这能解决问题。如果有任何不合理的地方,请发表评论,我会尽力澄清。现在已经很晚了,我的大脑感觉有点平淡。是时候来一头红牛了。
“这意味着查询引擎必须采取额外的步骤来定位实际数据。”
不一定 - 如果索引覆盖给定查询,则不必访问数据页。此外,对于包含的列,可以将额外的列添加到非聚集索引中,使其覆盖而不改变键大小。
所以最终的答案是 - 它取决于(取决于比你在一个问题中真正涵盖的更多的信息) - 您需要了解索引的所有功能,并且给定查询的执行计划可能与您的期望不同。
我的一般经验法则是,表始终具有聚集索引(通常在标识或顺序 GUID 上),但为了性能而添加非聚集索引。但总有例外——堆表有一席之地,更广泛的聚集索引也有一席之地。看似冗余的索引更窄以适合每页更多的行。等等等等
而且我不会担心允许的各种索引的限制——这几乎肯定不会在许多现实世界的例子中发挥作用。