当优化器所选择的非聚簇索引只包含查询请求的一部分字段时,就需要一个查找(lookup)来检索其他字段来满足请求。对一个有聚簇索引的表来说是一个键查找(key lookup),对一个堆表来说是一个RID查找(RID lookup)。这种查找即是——书签查找。
书签查找根据索引的行定位器从表中读取数据。故此,除了索引页面的逻辑读取外,还需要数据页面的逻辑读取。如果查询的结果是大数据集,建议使用聚簇索引。聚簇索引不用书签查找,因为叶子页面和数据页相同。
看下面的实例(AdventureWorks):
select *
from Sales.SalesOrderDetail as sod
where sod.ProductID = 776
列ProductID是表上的非聚簇索引,
CREATE NONCLUSTERED INDEX [ IX_SalesOrderDetail_ProductID ] ON [ Sales ] . [ SalesOrderDetail ]
(
[ ProductID ] ASC
)
非聚簇索IX_SalesOrderDetail_ProductID包含列ProductID以及组成聚簇索引的
列SalesOrderId,SalesOrderDetailId(SalesOrderId,SalesOrderDetailId是表上的复合主键,
因为非聚簇索引的叶子节点是聚簇索引),并没有覆盖查询的所有列,故此需要一个书签查找。
ProductID=776查询结果是228行,但是随着结果集的增大,需要书签查找的非聚簇索引将会被优化器放弃而采用聚簇索引扫描,如:
select *
from Sales.SalesOrderDetail as sod
where sod.ProductID = 793
此查询结果集是705行,
表'SalesOrderDetail'。扫描计数1,逻辑读取1240 次
但是如果在表上强制采取非聚簇索引:
表'SalesOrderDetail'。扫描计数1,逻辑读取2173 次
下面再看一具体实例,来揭示书签查找成因以及避免方法。
select e.NationalIDNumber,e.Title,e.HireDate
from HumanResources.Employee as e
where e.NationalIDNumber = ' 693168613 '
HumanResources.Employee表有一非聚簇索引:AK_Employee_NationalIDNumber([NationalIDNumber] ASC),执行后的计划:
为了避免这个书签查找,应该把查询中引用的字段Title和HireDate添加到非聚簇索引中,
create unique nonclustered index ak_employee_Nationalidnumber
on HumanResources.Employee
(
NationalIDNumber asc ,Title asc ,HireDate asc
)
with drop_existing
执行查询后的执行计划:
另一避免书签查找的方法是用覆盖索引,
create unique nonclustered index ak_employee_Nationalidnumber
on HumanResources.Employee
(
NationalIDNumber asc
)include(title,hiredate)
with drop_existing
执行计划图: