在我们讲解这个案例前,我们先来了解/预热一下SQL Server的两个概念:键查找(key lookup)和RID查找(RID lookup),通常,当查询优化器使用非聚集索引进行查找时,如果所选择的列或查询条件中的列只部分包含在使用的非聚集索引和聚集索引中时,就需要一个查找(lookup)来检索其他字段来满足请求。对一个有聚簇索引的表来说是一个键查找(key lookup),对一个堆表来说是一个RID查找(RID lookup),这种查找即是——书签查找(bookmark lookup)。在其他数据库概念中,可能又叫回表查询之类的概念。

那么我们先来构造案例所需的测试环境。下面测试环境为SQL Server 2014。

SELECT * INTO TEST FROM SYS.OBJECTS

CREATE CLUSTERED INDEX PK_TEST ON TEST(OBJECT_ID, NAME,CREATE_DATE)

CREATE INDEX IX_TEST_N1 ON TEST(PARENT_OBJECT_ID, TYPE)

UPDATE STATISTICS TEST WITH FULLSCAN;

如上所示,表TEST在字段OBJECT_ID, NAME,CREATE_DATE建立了聚集索引,然后下面这种查询语句,你查看其实际执行计划

SELECT OBJECT_ID, NAME,CREATE_DATE,PARENT_OBJECT_ID, TYPE 
FROM TEST WHERE PARENT_OBJECT_ID=2255213;

你会发现,SQL Server优化器走索引IX_TEST_N1查找就返回了所有数据。没有书签查找(回表查询),那么这是为什么呢?朋友这样问我的时候,我还真没有想明白。难道索引IX_TEST_N1中也会存储OBJECT_ID, NAME,CREATE_DATE的值? 当然你构造其它的案例时,有可能是索引IX_TEST_N1扫描就返回了数据。不会发生书签查找。

SQL Server索引查找/扫描没有出现key lookup的案例浅析_聚集索引

后面才想明白,非聚集索引中的索引行指向数据行的指针称为行定位器。 行定位器的结构取决于数据页是存储在堆中还是聚集表中。 对于堆,行定位器是指向行的指针。 对于聚集表,行定位器是聚集索引键。这是不是有点眼熟,类似于MySQL InnoDB的二级索引(Secondary Index)会自动补齐主键,将主键列追加到二级索引列后面。所以执行计划就走索引IX_TEST_N1查找就能返回数据了。根本不需要书签查找(回表查询)。如果查询语句多一个字段或者是SELECT *的话,你就会看到书签查找了。如下所示

SQL Server索引查找/扫描没有出现key lookup的案例浅析_聚集索引_02

PS:有些技术落下久了,感觉就生疏、荒废了一样。真的是业精于勤荒于嬉!