第六章——根据执行计划优化性能(3)——键值查找

简介: 原文: 第六章——根据执行计划优化性能(3)——键值查找 前言:         本文为本系列最后一篇,介绍键值查找的相关知识。         键值查找是具有聚集索引的表上的一个书签查找,键值查找用于SQLServer查询一些非键值列的数据。
原文: 第六章——根据执行计划优化性能(3)——键值查找

前言:

        本文为本系列最后一篇,介绍键值查找的相关知识。

        键值查找是具有聚集索引的表上的一个书签查找,键值查找用于SQLServer查询一些非键值列的数据。使用非聚集索引的查询不会有键值查找,但是所有键值查找会伴随非聚集索引出现。这里特别提醒的是键值查找总是伴有嵌套循环关联。

 

准备工作:

 

下面将创建一个表,通过执行计划看看键值查找的不同效果。为了产生键值查找,需要两件事情:

1、  聚集索引

2、  非聚集索引

 

当你在非聚集索引键值上有谓词时,查询的字段又不全部包含在非聚集索引上,需要通过聚集索引去查找,此时会产生键值查找。执行下面操作产生测试表:

 

USE AdventureWorks
GO


IF OBJECT_ID('SalesOrdDetailDemo') IS NOT NULL 
    BEGIN
        DROP TABLE SalesOrdDetailDemo
    END
GO



SELECT  *
INTO    SalesOrdDetailDemo
FROM    Sales.SalesOrderDetail
GO


 

 

步骤:

1、  在测试表SalesOrdDetailDemo上创建一个聚集索引和一个非聚集索引:

 

CREATE UNIQUE CLUSTERED INDEX idx_SalesDetail_SalesOrderID ON SalesOrdDetailDemo(SalesOrderID,SalesOrderDetailID)
GO

CREATE NONCLUSTERED INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo(ModifiedDate)
GO


 

 

2、  执行下面的查询,并开启实际执行计划:

 

SELECT  ModifiedDate
FROM    SalesOrdDetailDemo
WHERE   ModifiedDate = '2004-07-31 00:00:00.000'
GO


 

 

3、  从执行计划的截图中看到,使用了一个非聚集索引(执行计划中叫做索引)查找:

如果你使用了文本化的执行计划,会看到:

StmtText

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  |--Index Seek(OBJECT:([AdventureWorks].[dbo].[SalesOrdDetailDemo].[idx_non_clust_SalesOrdDetailDemo_ModifiedDate]), SEEK:([AdventureWorks].[dbo].[SalesOrdDetailDemo].[ModifiedDate]=CONVERT_IMPLICIT(datetime,[@1],0)) ORDERED FORWARD)

 

 

4、  对上面的查询语句进行少许的改动,多查询几列:

 

SELECT  ModifiedDate ,
        SalesOrderID ,
        SalesOrderDetailID
FROM    SalesOrdDetailDemo
WHERE   ModifiedDate = '2004-07-31 00:00:00.000'
GO


 

 

5、  再检查执行计划:

它的文本化执行计划如下:

StmtText

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  |--Index Seek(OBJECT:([AdventureWorks].[dbo].[SalesOrdDetailDemo].[idx_non_clust_SalesOrdDetailDemo_ModifiedDate]), SEEK:([AdventureWorks].[dbo].[SalesOrdDetailDemo].[ModifiedDate]=CONVERT_IMPLICIT(datetime,[@1],0)) ORDERED FORWARD)

 

 

6、  在上面的查询中添加的列均包含在聚集索引和非聚集索引中,现在增加更多的列:

 

SELECT  ModifiedDate ,
        SalesOrderID ,
        SalesOrderDetailID ,
        ProductID ,
        UnitPrice
FROM    SalesOrdDetailDemo
WHERE   ModifiedDate = '2004-07-31 00:00:00.000'
GO


 

 

7、  查看执行计划,此时出现了两个新的操作符——键值查找和嵌套循环,如图:

StmtText

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  |--Nested Loops(Inner Join, OUTER REFERENCES:([AdventureWorks].[dbo].[SalesOrdDetailDemo].[SalesOrderID], [AdventureWorks].[dbo].[SalesOrdDetailDemo].[SalesOrderDetailID], [Expr1004]) WITH UNORDERED PREFETCH)

       |--Index Seek(OBJECT:([AdventureWorks].[dbo].[SalesOrdDetailDemo].[idx_non_clust_SalesOrdDetailDemo_ModifiedDate]), SEEK:([AdventureWorks].[dbo].[SalesOrdDetailDemo].[ModifiedDate]='2004-07-31 00:00:00.000') ORDERED FORWARD)

       |--Clustered Index Seek(OBJECT:([AdventureWorks].[dbo].[SalesOrdDetailDemo].[idx_SalesDetail_SalesOrderID]), SEEK:([AdventureWorks].[dbo].[SalesOrdDetailDemo].[SalesOrderID]=[AdventureWorks].[dbo].[SalesOrdDetailDemo].[SalesOrderID] AND [AdventureWo

 

 

8、  同时可以看到在键值查找上的百分比相当高,此时先试一下使用hint来改变优化器的行为:

 

SELECT  ModifiedDate ,
        SalesOrderID ,
        SalesOrderDetailID ,
        ProductID ,
        UnitPrice
FROM    SalesOrdDetailDemo WITH ( INDEX=idx_SalesDetail_SalesOrderID )
WHERE   ModifiedDate = '2004-07-31 00:00:00.000'
GO


 

 

9、  此时优化器使用了聚集索引,但是不能在上面进行查找,只能扫描,如图:

10、上图中显示的聚集索引扫描在返回少量数据的时候并不高效,所以应该考虑就近是聚集索引扫描好还是键值查询好,现在来再开启SET STATISTICS IO 来监控一下IO情况,这次将三个查询都放到一起,其中两个是使用hint来分别把聚集索引和非聚集索引强制使用:

 

SET STATISTICS IO ON 
GO
SELECT  ModifiedDate ,
        SalesOrderID ,
        SalesOrderDetailID ,
        ProductID ,
        UnitPrice
FROM    SalesOrdDetailDemo 
WHERE   ModifiedDate = '2004-07-31 00:00:00.000'
GO

SELECT  ModifiedDate ,
        SalesOrderID ,
        SalesOrderDetailID ,
        ProductID ,
        UnitPrice
FROM    SalesOrdDetailDemo WITH ( INDEX=idx_SalesDetail_SalesOrderID )
WHERE   ModifiedDate = '2004-07-31 00:00:00.000'
GO


SELECT  ModifiedDate ,
        SalesOrderID ,
        SalesOrderDetailID ,
        ProductID ,
        UnitPrice
FROM    SalesOrdDetailDemo WITH ( INDEX=idx_non_clust_SalesOrdDetailDemo_ModifiedDate )
WHERE   ModifiedDate = '2004-07-31 00:00:00.000'
GO
SET STATISTICS IO OFF
GO


 

 

11、观察执行计划的开销情况:

然后观察一下IO情况:

12、通过对比,带有键值查找的非聚集索引貌似有更好的性能,但是如果移除了键值查找会不会更好?现在来尝试一下,这里先删除原有索引并创建一个覆盖索引或者带有INCLUDE列的索引。通知先清空一下缓存:

 

DROP INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo
GO

CREATE NONCLUSTERED INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo(ModifiedDate)
INCLUDE (ProductID,UnitPrice)
GO

--不要在生产环境执行下面语句:
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
GO


 

 

13、再次执行没有hint的查询

14、从执行计划中可以看到这次成功去除了键值查找:

同时可以观察到IO,发现从305次已经降到了3

 

分析:

        在第二步中,查询带有一个谓词来筛选ModifiedDate,所以非聚集索引将进行查找,且索引键上就有所需的数据,所以此时不需要再进行任何查找。

        在第四步中,在SELECT列中添加了SalesOrderIDSalesOrderDetailID,由于这两列在聚集索引中,所以此时依旧可以使用非聚集索引引用聚集索引的方式来实现。

        在第六步中,再次添加了新列,这些列不在任何索引的索引键中,所以非聚集索引必须通过聚集索引的叶子节点查找这两列新增列的数值,此时键值查找和嵌套循环关联就会出现。由于键值查找是高开销的操作,所以在第八步中使用了hint来强制优化器使用聚集索引。但是此时使用了聚集索引扫描而不是查找,所以现在要思考哪种方式更快?

        为了得到答案,在第十步中把三个查询放到一起。一个是没有hint,一个是使用聚集索引hint,另外一个使用非聚集索引hint

从第十一步的百分比看到,SQLServer使用了带有键值查找的非聚集索引来代替聚集索引扫描。

现在可以初步得出带有键值查找的非聚集索引查找比较快,但是是否有更快的方法?

因为UnitPriceProductID不在的时候键值查找会消失,但是有时候确实需要这些列,所以使用覆盖索引或者带有INCLUDE列的非聚集索引来代替普通的非聚集索引。通过1213步可以看出已经移除了键值查找并有更好的性能。

出现键值查找的主要原因之一是因为谓词中出现了符合非聚集索引的规则,但是在SELECT中的字段不存在于聚集索引键值或者非聚集索引键值中。此时聚集索引必须通过键值查找来找出这些数据。

目录
相关文章
|
22天前
|
缓存 关系型数据库 MySQL
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
|
22天前
|
缓存 关系型数据库 MySQL
MySQL 查询优化:提速查询效率的13大秘籍(索引设计、查询优化、缓存策略、子查询优化以及定期表分析和优化)(中)
MySQL 查询优化:提速查询效率的13大秘籍(索引设计、查询优化、缓存策略、子查询优化以及定期表分析和优化)(中)
|
18天前
|
存储 关系型数据库 MySQL
mysql索引优化,更好的创建和使用索引
mysql索引优化,更好的创建和使用索引
|
4月前
|
SQL 存储 关系型数据库
⑩② 【MySQL索引】详解MySQL`索引`:结构、分类、性能分析、设计及使用规则。
⑩② 【MySQL索引】详解MySQL`索引`:结构、分类、性能分析、设计及使用规则。
36 0
|
7月前
|
存储 人工智能 测试技术
唯一索引比普通索引快吗?运行原理是什么?
唯一索引比普通索引快吗?运行原理是什么?
74 0
|
10月前
|
存储 关系型数据库 MySQL
第七章 创建⾼性能的索引
第七章 创建⾼性能的索引
|
存储 SQL 关系型数据库
索引到底能提升多少查询效率?何时该使用索引?一文快速搞懂数据库索引及合理使用它
索引到底能提升多少查询效率?何时该使用索引?一文快速搞懂数据库索引及合理使用它
447 0
索引到底能提升多少查询效率?何时该使用索引?一文快速搞懂数据库索引及合理使用它
|
SQL 存储 缓存
索引不是越多越好,理解索引结构原理,才有助于我们建立合适的索引!
MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等。为了避免混乱,本文将只关注于BTree索引,因为这是平常使用MySQL时主要打交道的索引。
589 0
|
存储 缓存 索引
性能优化技巧 - 查找
SPL为用户提供了强大的索引机制以及针对不同场景中各对象的查询函数,善加运用,可以显著提高查询性能。 1 键值查找1.1 序表我们先建立一个份“通话记录”的模拟数据,通过这份数据,来比较一下不同查询函数对序表查询性能的影响。
914 0
|
SQL 关系型数据库 数据库
MySQL优化系列(二)--查找优化(1)(非索引设计)
MySQL优化系列(二)--查找优化(1)(非索引设计) 接下来这篇是查询优化,用户80%的操作基本都在查询,我们有什么理由不去优化他呢??所以这篇博客将会讲解大量的查询优化(索引以及库表结构优化等高级用法后面文章再讲),先讲单表查优化,再讲多表查优化。
1763 0