“覆盖索引使您能够避免返回到表中以满足请求的所有列,因为所有请求的列都已经存在于非聚集索引中。这意味着您还可以避免返回到表中进行任何逻辑或物理的信息读取。”
然而,以上这不是我想要传达的全部意思,因为他不仅仅是避免逻辑或物理的读取的问题。在“非聚集索引”中的列和需要在表中查找的列之间,还需要考虑“将数据放在一起”的必要工作。为了说明这个问题,让我们创建两个完全一模一样的表,即:相同的结构,相同的数据且都是10000条:
CREATE TABLE Beach.dbo.cruiser_1 ( pkid INT IDENTITY(1,1) NOT NULL, registration_id VARCHAR(20) NOT NULL, last_name VARCHAR(50) NOT NULL, first_name VARCHAR(50) NOT NULL, address_1 VARCHAR(100) NULL, address_2 VARCHAR(100) NULL, city VARCHAR(100) NULL, state_region VARCHAR(100) NULL, postal_code VARCHAR(10) NULL, company_id VARCHAR(20) NULL, plus_one_notes VARCHAR(200) NULL, registration_notes VARCHAR(200) NULL ); CREATE TABLE Beach.dbo.cruiser_2 ( pkid INT IDENTITY(1,1) NOT NULL, registration_id VARCHAR(20) NOT NULL, last_name VARCHAR(50) NOT NULL, first_name VARCHAR(50) NOT NULL, address_1 VARCHAR(100) NULL, address_2 VARCHAR(100) NULL, city VARCHAR(100) NULL, state_region VARCHAR(100) NULL, postal_code VARCHAR(10) NULL, company_id VARCHAR(20) NULL, plus_one_notes VARCHAR(200) NULL, registration_notes VARCHAR(200) NULL );
请相信我,两个表中的数据是完全一样的。在此我们就暂时不把每个表10000条的数据展示了。同时,我已经在每个表上创建了完全一样的两个聚集索引,且都在pkid列上,
因为该列为自动增长列,我也把pkid列上的填充因子设置为了100%.
ALTER TABLE dbo.cruiser_1 ADD CONSTRAINT PK_cruiser_1_pkid PRIMARY KEY CLUSTERED ( pkid ) WITH ( PAD_INDEX = OFF , FILLFACTOR = 100 , STATISTICS_NORECOMPUTE = OFF , IGNORE_DUP_KEY = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]; GO ALTER TABLE dbo.cruiser_2 ADD CONSTRAINT PK_cruiser_1_pkid PRIMARY KEY CLUSTERED ( pkid ) WITH ( PAD_INDEX = OFF , FILLFACTOR = 100 , STATISTICS_NORECOMPUTE = OFF , IGNORE_DUP_KEY = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]; GO
最后,让我们在两个表之间引入一些不同的东西。dbo.cruiser_1是一个建立在 last_name和first_name列且具有包含列registration_id的非聚集索引.dbo.cruiser_2是与索引dbo.cruiser_1相同的非聚集索引但没有包含列,如下所示.
CREATE NONCLUSTERED INDEX [IX_cruiser_1_last_name] ON [dbo].[cruiser_1] ( [last_name] ASC, [first_name] ASC ) INCLUDE ( [registration_id] ) WITH ( PAD_INDEX = OFF , STATISTICS_NORECOMPUTE = OFF , SORT_IN_TEMPDB = OFF , DROP_EXISTING = OFF , ONLINE = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON , FILLFACTOR = 80 ) GO CREATE NONCLUSTERED INDEX [IX_cruiser_2_last_name] ON [dbo].[cruiser_2] ( [last_name] ASC, [first_name] ASC ) WITH ( PAD_INDEX = OFF , STATISTICS_NORECOMPUTE = OFF , SORT_IN_TEMPDB = OFF , DROP_EXISTING = OFF , ONLINE = OFF , ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON , FILLFACTOR = 80 ) GO
让我们重新回顾一下:我们有两个完全一样的table,每个表都有10000条记录。唯一的不同就是他们单一的非聚集索引,一个有包含列,另一个没有包含列。他们还有完全一样的主键及聚集索引。
如果我在有包含列的表上提交以下的查询,我们会看到以下的执行计划:
FROM Cruiser_1
WHERE last_name = 'Jones' AND first_name = 'Mary';
上述操作都发生在非聚集索引的内部。我们也不需要回到表上获取更多的数据。如果我们执行完全一样的查询在具有同样索引结构的表上,在非聚集索引列除去了包含列,我们得到了不同的行为:
FROM Cruiser_2
WHERE last_name = 'Jones' AND first_name = 'Mary';
因为上述的非聚集索引并没有包含我们所需的所有信息来满足当前的查询,所以它有必要针对索引来获取基本信息,并且在非聚集索引上使用行指针,从表中获取registration_id并且运行一个嵌套循环,其目的是为了使两组工作数据在返回给终端用户之前结合在一起。针对上述查询,我们可以通过查看I/O统计来发现消耗在数据读取上不同的开销:
请注意,这些都是逻辑读取,因为页面已经在缓冲区中。如果有这么一个情况,它是一个更大的数据集,如果你想要使用的页面仍然在活动服务器的磁盘上,这可能一个非常昂贵的操作。
我希望上述操作能更好地描述了我的观点! 附:原文链接!