上个星期我讨论了SQL Server里的聚集索引。当你在表上定义了一个聚集索引,你是物理上把你的表数据按提供的聚集键列的顺序存储。在SQL Server里,一个表只能定义一个聚集索引,非聚集索引可以定义多个(最多999个)。
非聚集索引是第二索引,你可以在表上列进行定义。你也可以把非聚集索引与书比较。但是这次你把它认为类似T-SQL 语言参考的书。书本身就是一个聚集索引,不同的T-SQL命令是按它们的名字物理排序的。在书的最后,你会看到一个索引。当你查找一个T-SQL 命令(例如 CREATE TABLE),你可以使用书最后的索引,来找到这个命令详细介绍的位置。
这里书会给你一个查找值——页码,在那里你可以找到这个命令的详细信息。这与SQL Server里(非聚集索引)的概念是一样的:但给你在执行计划里通过非聚集索引访问你的表,SQL Server会在非聚集索引的叶子层给你查找值,你可以用它找到这条记录的更多信息。SQL Server需要用这个查找值做导航,从非聚集索引到聚集索引或堆表里找到记录其他列值,这些列不是非聚集索引的一部分。在SQL Server里这个被称为书签查找(Bookmark Lookup)。我们来看看它的更多细节。
书签查找(Bookmark Lookups)
每次不在查询的执行计划里访问非聚集索引,你查询里的一些列不是非聚集索引的一部分,SQL Server需要在执行计划里进行书签查找操作。下图是一个执行计划里典型的书签查找:
可以看到,SQL Server在Person.Address表里进行非聚集查找操作。另外SQL Server通过键查找(Key Lookup)(聚集的)操作从聚集表获取所有其他列。这个看起来是SQL Server里很酷的功能,但是实际上,书签查找是非常,非常,非常危险的!
它们会导致书签查找死锁,性能会受老的过期的统计信息影响,当你与参数嗅探问题(Parameter Sniffing )打交道时也是。书签查找只会在与非聚集索引组合时发生。因此,下星期我们会讨论下在执行计划里如何避免书签查找,还有为什么有时候SQL Server会完全忽略你的近乎完美的非聚集索引。
聚集键依赖关系(Clustered Key Dependency)
像我刚才说过的,SQL Server在非聚集索引的叶子层保存查找值,用来指向存在聚集表或堆表的记录。当你在堆表定义了一个非聚集索引,这个查找值称为行标识者(Row-Identifier)查找值。它是8 bytes长的值,包含记录物理存储的页号(4 bytes),文件号(2 bytes),还有槽号(2 bytes)。
如果你在聚集表上定义你的非聚集索引,SQL Server使用聚集键值作为查找值。这意味你你要认真选择的聚集键列都是每个非聚集索引的一部分。在聚集和非聚集索引之间有着巨大的依赖关系。聚集键基本上是你表里的冗余数据。因此,当你选择聚集键列时,你真的需要认真考虑。因为它的强大依赖性,选择的最佳聚集键应该有3个特性:
- 唯一的(Unique)
- 范围小的(Narrow)
- 静态的(Static)
用心记住它们,因为你的聚集键始终出现在每个非聚集索引里。
小结
非聚集索引对提高你的查询性能非常重要。不好非聚集索引的设计会让你引入书签查找,这会引入巨大的问题和副作用到你的数据库里。如果你想对非聚集索引内部结构有更深入的理解,可以看看索引深入浅出:非聚集索引的B树结构在聚集表。
如我答应的,下星期我会讲下使用覆盖非聚集索引(Covering Non-Clustered Indexes)来避免书签查找(Bookmark Lookups)。还有卸载点(Tipping Point),它用来定义SQL Server是否在使用非聚集索引。请继续关注!