为什么在 SQL 索引中使用 INCLUDE
-
19-09-2019 - |
题
我最近在我维护的数据库中遇到了一个索引,其形式为:
CREATE INDEX [IX_Foo] ON [Foo]
( Id ASC )
INCLUDE
( SubId )
在这种特殊情况下,我遇到的性能问题(对 Id 和 SubId 的 SELECT 过滤速度很慢)可以通过简单地将 SubId 列移动到适当的索引中而不是作为包含列来解决。
然而,这让我想到,我根本不理解包含列背后的推理,通常情况下,它们可能只是索引本身的一部分。即使我并不特别关心索引本身中的项目,在索引中包含列而不是简单地包含列也有任何缺点。
经过一些研究,我意识到索引列中的内容有很多限制(索引的最大宽度,以及某些无法索引的列类型,如“图像”)。在这些情况下,我可以看到您将被迫在索引页数据中包含该列。
我唯一能想到的是,如果 SubId 有更新,如果包含该列,则不需要重新定位该行(尽管需要更改索引中的值)。我还缺少其他东西吗?
我正在考虑检查数据库中的其他索引,并在可能的情况下适当移动索引中包含的列。这会是一个错误吗?
我主要对 MS SQL Server 感兴趣,但也欢迎有关其他数据库引擎的信息。
解决方案
到目前为止,答案都是正确的,但它们可能不足以传达您从覆盖索引中获得的信息。
就你而言,你有一张桌子 Foo
和一些字段,包括 Id
(我假设是主键),以及 SubId
这是某种附加的 ID。
你还有一个索引 IX_Foo
我认为只有 Id
暂时在其中。
所以现在你需要找到 SubId
为了 Id=4
.
SELECT Id, SubId
FROM Foo
WHERE Id=4
- SQL Server 将查看 SELECT 语句并确定它可以使用
IX_Foo
- 然后它会去搜索该值
Id=4
在你的索引中IX_Foo
- 当它找到它时,它现在需要的值
SubId
, , 也 - 非聚集索引
IX_Foo
将包含聚类键值 - 使用该集群键值,SQL Server 将执行“书签查找”来定位整个数据行所在的实际数据页
- 它将获取该页面并提取其值
SubId
从中 - 它将返回这些值来满足您的查询
这里的要点是:一旦 SQL Server 找到您的 Id=4
在里面 IX_Foo
索引,然后需要执行另一个 I/O 操作,即书签查找,以获取整个数据行,以便能够找到 SubId
价值。
如果您有覆盖索引,例如 IX_Foo
还包括 SubId
, ,消除了进行书签查找的额外 I/O。一旦值 Id=4
发现于 IX_Foo
索引,非聚集索引中的索引页还将包含以下值 SubId
- SQL Server 现在可以返回您在 SELECT 查询中要求的两个值 没有 必须进行额外的(可能很昂贵,因此很慢)书签查找才能获取另一个 Id 列。
这是覆盖索引的主要好处 - 如果您只需要一两个额外的列,除了要查找的索引值之外,通过将这些值包含到索引本身中,您可以节省大量书签查找,从而显着加快速度。但是,您应该只包含很少的少量信息 - 不要将整个数据行复制到所有非聚集索引中!这不是重点。
更新: 权衡是这样的:如果您在 (Id, SubId) 上有索引,则索引中的所有页面都具有两列 - 整个索引树。
如果您 INCLUDE(SubId),则 SubId 字段仅出现在叶级别。
这意味着
- SQL Server 无法搜索和比较 SubId(值不在索引树中)
- 使用较少的空间,因为这些值仅位于叶级别
其他提示
之所以具有在索引的附加列是这样,当你做一个查询,只需要由索引使用的列则可以通过本身实现从索引查询。这样你节省一些时间和资源去回表。发生这种情况时,我们说指数是查询的覆盖的指数。
您可能不希望使“指标适当”的这一附加列部分原因是因为当你插入或更新该列,你就更有可能需要重新排序指标的部分。
使用包括在索引允许索引被用作覆盖索引(即某些查询可以单独使用该索引来满足,而不必执行书签查找到聚集索引),不添加这些列于实际该指数的树的一部分,从而保持了指数下行的大小。 (所包含的列仅添加到索引的叶节点)。