第六章——根据执行计划优化性能(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中的字段不存在于聚集索引键值或者非聚集索引键值中。此时聚集索引必须通过键值查找来找出这些数据。

时间: 2024-10-13 06:06:22

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

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

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

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

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

数据库系统实现 第六章 查询执行

第六章 查询执行 查询执行也就是操作数据库的算法 一次查询的过程: 查询-->查询编译(第七章)-->查询执行(第六章)-->数据 查询编译预览 查询编译可以分为三个步骤: a)分析:构造分析树,用来表达查询和它的结构 b)查询重写,分析树被转化为初始查询计划,通常是代数表达式,之后初始的查询计划会被优化为一个时间更小的计划 c)物理计划生成,将查询计划转化成物理的计划, 为了选择更好的查询计划,需要判断 1)查询哪一个代数的等价形式是最有效的 2)对选中形式的每一个操作,所使用的算法选

SQL Server执行计划那些事儿(2)——查找和扫描

接下来的文章是记录自己曾经的盲点,同时也透漏了自己的发展历程(可能发展也算不上,只能说是瞎混).当然,一些盲点也在工作和探究过程中慢慢有些眉目,现在也愿意发扬博客园的奉献精神,拿出来和大家分享一下. 开门见上,直接入题 在查看执行计划时候,你是否曾经也和我一样,有这样的疑惑呢?查找和扫描究竟是什么,以及他们的在查询性能上有什么区别.下面分享下我的理解. 扫描和查找是SQL Server从表或索引中读取数据所采用的迭代器,又因为经常在执行计划中看到,因此理解他们之间的区别,对我们优化查询有很重要的

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; 在SQL D

第六章 javaScript执行环境和作用域

这个只是点对于初学者其实大概了解就可以,但是要研究明白javaScript的机制,就是非常必要的,这只是我的一些记录,大家参考即可,如有错误请指出. 执行环境的概念是javaScript一个虚拟的概念,如何定义它呢?它的作用又是什么呢?它是怎么组成的呢? 大家都比较认可的说法:执行环境又称为执行上下文,从实际的表现来看,可以把它理解为由“对象”组成的一个堆栈.既然是堆栈,就是先入后出了. 组成堆栈的对象是什么对象?我没有找到确切的定义,基于我自己的理解,这个对象是一个自定义对象,里边包含有变量.

第六章 任务执行

6.1 在线程中执行任务 围绕任务执\执行设计应用程序结构 .讲一个复杂的功能分解为多个独立的任务. 并可以并行执行, 在调度和负载均衡过程中实现更高的灵活性. 6.1.1 串行的执行任务 在单个线程中串行的执行各项任务. 6.1.2 显示的创建任务 正常负载情况下, 为每个任务分配一个线程可以提升串行执行能力. 6.1.3 无限制创建线程的不足 线程生命周期的开销非常高. 资源消耗 . 活跃的线程会消耗系统资源 , 尤其是内存 . 可创建的线程数量存在限制. 6.2 Executor框架 任务

jdk8新特性--使用lambda表达式的延迟执行特性优化性能

使用lambda表达式的延迟加载特性对代码进行优化: 原文地址:https://www.cnblogs.com/niwotaxuexiba/p/10850339.html

[Java 并发] Java并发编程实践 思维导图 - 第六章 任务执行

根据<Java并发编程实践>一书整理的思维导图.希望能够有所帮助. 第一部分: 第二部分: 第三部分: