我要问的第一件事是,这样大小的表是否有充分的理由没有聚集索引?聚集键甚至不必是唯一的(如果不是,SQL Server 会为其添加一个“唯一标识符”,尽管通常最好使用 IDENTITY 列)。
回答你的两个问题:
1) 索引建议与您正在运行的查询有关。根据经验,建议的列将与查询优化器用来探测表的列相匹配,因此如果您有如下查询:
SELECT field1, field2, field3
FROM table1
WHERE field4 = 1 AND field5 = 'bob'
建议的索引可能位于field4 和field5 列上,并按选择性顺序排列(即,值变化最大的列在前)。它可能包括其他列(例如field1, field2, field3),因为这样查询优化器只需访问索引即可获取该数据,而无需访问数据页面。
另请注意,有时建议的索引并不总是您自己选择的索引。如果连接多个表,查询优化器将根据可用的索引和统计信息选择它认为最适合数据的执行计划。它可能会遍历一个表并探测另一个表,而最好的计划可能会以相反的方式进行。您必须检查实际的查询执行计划以了解发生了什么。
如果您知道您的查询具有足够的选择性,可以深入到小范围的记录(例如,有一个类似 WHERE table1.field1 = 1 AND table1.field2 = 'abc' AND table1.field3 = '2015-07-01' ... 的 where 子句),您可以添加一个涵盖所有引用列的索引。这可能会影响查询优化器扫描此索引以获取少量行以连接到另一个表,而不是执行扫描。
根据经验,在检查执行计划时,一个很好的起点是尝试消除扫描,在这种情况下,服务器将读取大量行,并提供索引来缩小必须执行的数据量进行处理。
2) 我认为其他人现在可能已经很好地解释了这一点 - 包含的列在那里,因此当读取索引时,服务器不必读取数据页来获取这些值;它们也存储在索引中。
很多人在阅读到此类“覆盖索引”时的最初反应可能是“我为什么不添加一大堆这样做的索引”,或者“我为什么不添加一个索引覆盖所有列”。
在某些情况下(通常是具有窄列的小表,例如多对多连接表),这很有用。但是,您添加的每个索引都会带来一些成本:
首先,每次更新或向表中插入值时,都必须更新索引。这意味着您将不得不应对锁定、锁定升级问题(可能是死锁)、页面拆分和相关的碎片。有多种方法可以缓解这些问题,例如使用适当的填充因子来允许将更多值插入到索引页面中,而无需拆分它。
其次,索引占用空间。至少,索引将包含您使用的键值以及 RID(在堆中)或聚集键(在具有聚集索引的表中)。覆盖索引还包含包含列的副本。如果这些是大列(例如大 varchars),那么索引可能会非常大,并且表索引加起来大于表本身并不是闻所未闻的。请注意,索引的大小也有限制,包括列和总大小。因为聚集键总是包含在具有聚集索引的表的非聚集索引中(聚集索引在数据页本身上),这意味着聚集键越小越好。虽然您可以使用复合索引,但这可能是几个字节宽,虽然您可以使用非唯一键,但 SQL Server 会向其中添加该唯一符,即另外 4 个字节。最佳实践是使用标识列(如果您设想表中的行数超过 20 亿,则为 int 或 bigint)。标识也总是递增,因此在插入新记录时不会在数据页中出现页面拆分,因为它始终位于表的末尾。
所以 tl;dr;是:
建议的索引可能很有用,但通常不会给出最佳索引。如果您知道数据的结构以及如何查询数据,则可以构建包含常用探测键的索引。
始终按照选择性的顺序对索引中的列进行排序(即具有最多值的列在前)。这可能看起来违反直觉,但它允许 SQL Server 以更少的读取速度更快地找到您想要的数据。
包含的列很有用,但通常只在它们是小列时(例如整数)。如果您的查询需要一个表中的六列,而索引只覆盖其中的五列,SQL Server 仍然需要访问数据页,所以在这种情况下,最好不要包含包含的列,因为它们只占用空间并且具有维护成本。