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

原文:第六章——根据执行计划优化性能(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列中添加了SalesOrderID和SalesOrderDetailID,由于这两列在聚集索引中,所以此时依旧可以使用非聚集索引引用聚集索引的方式来实现。

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

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

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

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

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

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

时间: 2025-01-28 19:03:30

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

第六章——根据执行计划优化性能(2)——查找表/索引扫描

原文:第六章--根据执行计划优化性能(2)--查找表/索引扫描 前言:       在绝大部分情况下,特别是从一个大表中返回少量数据时,表扫描或者索引扫描并不是一种高效的方式.这些必须找出来并解决它们从而提高性能,因为扫描将遍历每一行,查找符合条件的数据,然后返回结果.这种处理是相当耗时耗资源的.在性能优化过程中,一般集中于: 1.  CPU 2.  Network 3.  磁盘IO 而扫描操作会增加这三种资源的开销.   准备工作: 下面将创建两个表来查看不同的物理关联操作的不同影响.创建脚本

第六章——根据执行计划优化性能(1)——理解哈希、合并、嵌套循环连接策略

原文:第六章--根据执行计划优化性能(1)--理解哈希.合并.嵌套循环连接策略 前言: 本系列文章包括: 1. 理解Hash.Merge.Nested Loop关联策略. 2. 在执行计划中发现并解决表/索引扫描. 3. 介绍并在执行计划中发现键查找并解决它们.   对于性能优化,需要集中处理以下的问题: 1. 为你的环境创建性能基线. 2. 监控现在的性能并发现瓶颈. 3. 解决瓶颈以便得到更好的性能.   一个预估执行计划是描述查询将会如何执行的一个蓝图,而一个实际执行计划就是一个查询执行时

通过分析SQL语句的执行计划优化SQL_MsSql

如何干预执行计划 - - 使用hints提示 基于代价的优化器是很聪明的,在绝大多数情况下它会选择正确的优化器,减轻了DBA的负担.但有时它也聪明反被聪明误,选择了很差的执行计划,使某个语句的执行变得奇慢无比.此时就需要DBA进行人为的干预,告诉优化器使用我们指定的存取路径或连接类型生成执行计划,从而使语句高效的运行.例如,如果我们认为对于一个特定的语句,执行全表扫描要比执行索引扫描更有效,则我们就可以指示优化器使用全表扫描.在Oracle中,是通过为语句添加hints(提示)来实现干预优化器优

Pig源码分析: 逻辑执行计划优化

Whole View 本文分析的是逻辑执行计划优化的代码结构,具体每种Rule的实现不做分析. 看本文之前最好参考之前那篇逻辑执行计划模型的文章. Architecture 几个关键类/接口的关系: 每个关键类/接口的实现和继承结构在下面各节展开. Optimizer PlanOptimizer是抽象类,主要和Rule.PlanTransformListener.OperatorPlan打交道. public abstract class PlanOptimizer { protected Li

ORACLE实际执行计划与预估执行计划不一致性能优化案例

  在一台ORACLE服务器上做巡检时,使用下面SQL找出DISK_READ最高的TOP SQL分析时,分析过程中,有一条SQL语句的一些反常现象,让人觉得很奇怪:   SELECT SQL_ID,        SQL_TEXT,        DISK_READS,        BUFFER_GETS,        PARSING_SCHEMA_NAME,        EXECUTIONS FROM   V$SQLAREA ORDER  BY DISK_READS DESC;   在S

通过分析SQL语句的执行计划优化SQL(二)

优化|语句|执行 第5章 ORACLE的执行计划 背景知识:        为了更好的进行下面的内容我们必须了解一些概念性的术语: 共享sql语句    为了不重复解析相同的SQL语句(因为解析操作比较费资源,会导致性能下降),在第一次解析之后,ORACLE将SQL语句及解析后得到的执行计划存放在内存中.这块位于系统全局区域SGA(system global area)的共享池(shared buffer pool)中的内存可以被所有的数据库用户共享.因此,当你执行一个SQL语句(有时被称为一个

通过分析SQL语句的执行计划优化SQL

如何干预执行计划 - - 使用hints提示 基于代价的优化器是很聪明的,在绝大多数情况下它会选择正确的优化器,减轻了DBA的负担.但有时它也聪明反被聪明误,选择了很差的执行计划,使某个语句的执行变得奇慢无比.此时就需要DBA进行人为的干预,告诉优化器使用我们指定的存取路径或连接类型生成执行计划,从而使语句高效的运行.例如,如果我们认为对于一个特定的语句,执行全表扫描要比执行索引扫描更有效,则我们就可以指示优化器使用全表扫描.在Oracle中,是通过为语句添加hints(提示)来实现干预优化器优

mysql 执行计划优化

一条简单的SQL 语句竟花了15.87 sec, 写道 mysql> SELECT x.loc AS loc, x.lastmod AS lastmod, x.changefreq AS changefreq, x.changecount AS changecount, x.priority AS priority, x.language AS language, x.ac cess AS access, x.status AS status FROM xmlsitemap x WHERE (x

《Oracle高性能SQL引擎剖析:SQL优化与调优机制详解》一1.2 显示执行计划

1.2 显示执行计划 我们现在知道,有三个途径可以获取查询计划:v$sql_plan.dba_hist_sql_plan和PLAN_TABLE.如果需要读取一条SQL语句的执行计划,就需要知道该条语句的SQL_ID,如果该语句存在多个游标或者执行计划,则还需要知道游标的CHILD_NUMBER或计划的哈希值(可选).而无论我们通过哪个途径来获取执行计划,显示方式主要是两种:语句查询和包DBMS_XPLAN显示. 1.2.1 通过查询语句显示计划 通过查询语句从一些视图里读出执行计划并作格式化输出