运行计划中的三种 Join 策略

SQL Server 存在三种 Join 策略:Hash Join,Merge Join,Nested Loop Join。

Hash Join:用来处理没有排过序/没有索引的数据,它在内存中把 Join 两边数据(的关联key)分别建立一个哈希表。比如有下面的查询语句,关联的两张表没有建立索引,运行计划将显示为Hash Join。

SELECT
sh.*
FROM
SalesOrdHeaderDemo AS sh
JOIN
SalesOrdDetailDemo AS sd
ON
sh.SalesOrderID=sd.SalesOrderID
GO

Merge Join:用来处理有索引的数据,它比Hash Join轻量化。我们为前面两张表的关联列建立索引,然后再次上面的查询,运行计划将变更为Merge Join

CREATE UNIQUE CLUSTERED INDEX idx_salesorderheaderdemo_SalesOrderID ON SalesOrdHeaderDemo (SalesOrderID)
GO
CREATE UNIQUE CLUSTERED INDEX idx_SalesDetail_SalesOrderlID ON SalesOrdDetailDemo (SalesOrderID,SalesOrderDetailID)
GO

Nested Loop Join:在满足Merge Join的基础上,假设某一边的数据较少,那么SQL Server 会把数据较少的那个作为外部循环,还有一个作为内部循环来完毕Join处理。继续前面的样例为查询语句加上WHERE语句来降低 Join 一边的数据量,运行计划显示为Nested Loop Join。

SELECT
sh.*
FROM
SalesOrdHeaderDemo AS sh
JOIN
SalesOrdDetailDemo AS sd
ON
sh.SalesOrderID=sd.SalesOrderID
WHERE
sh.SalesOrderID=43659

运行计划中的(table/index scan)的改进

在很多场合我们须要在一张包括很多数据的表中提取出一小部分数据,此时应当避免Scan,由于扫描处理会遍历每一行,这是相当耗时耗力的。以下我们来看一个样例:

SELECT
sh.SalesOrderID
FROM
SalesOrdHeaderDemo AS sh
JOIN
SalesOrdDetailDemo AS sd
ON
sh.SalesOrderID=sd.SalesOrderID
WHERE
sh.OrderDate='2005-07-01 00:00:00.000'
GO

图中的红圈标出了table scan,而且运行计划也智能得建议建立索引。我们先尝试在SalesOrdHeader 表上建立一个索引:

CREATE UNIQUE CLUSTERED INDEX idx_salesorderheaderdemo_SalesOrderID ON SalesOrdHeaderDemo (SalesOrderID)
GO

然后再次运行同样的查询语句,运行计划变成下面的模样:

table scan 变为了 Index Scan,继续给还有一张表也加上索引:

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

运行计划发生下面的变化:

尽管不能说 Scan 比 Seek 差,但绝大多数的场合(尤其是在很多数据中查找少量数据时)Seek 是更好的选择。举例来说假设你有一个上亿条数据的表,你要取当中的100条,那么你应当保证其採用 Seek,但假设你须要取出当中绝大多数(比方95%)的数据时,Scan 可能更好。(有较权威的文章给出了这个阀值为30%,即取出超过30%数据时 scan 更高效;反之则 Seek 更好)

另外你可能注意到两张表上都建立了索引但一张表在运行计划中表现为 Clustered index scan,而还有一张表现为 Clustered index seek,我们期待的不是两个 Clustered index seek 吗?这是由于前一张表没有断言(predicate),而后一张表通过 ON keyword对SalesOrderID 进行了断言限制。

运行计划中的 Key Lookup

为了兴许的演示样例,我们先在同一张表上建立两个不同的索引:

CREATE UNIQUE CLUSTERED INDEX idx_SalesDetail_SalesOrderlID ON SalesOrdDetailDemo (SalesOrderID,SalesOrderDetailID)
GO
CREATE NONCLUSTERED INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo(ModifiedDate)
GO

运行下面的查询:

SELECT
ModifiedDate
FROM SalesOrdDetailDemo
WHERE ModifiedDate='2005-07-01 00:00:00.000'
GO

运行计划例如以下图,他利用了我们先前建立在 ModifiedDate 字段上的 Non-Clustered Index,生成为一个Index Seek 处理。

我们改造一下查询语句,SELECT 中多加两个字段:

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

运行计划例如以下图,基本没变:

上面选出的字段不是属于 Non-Clustered Index 就是属于 Clustered Index,假设再添加几个其它的字段呢?

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

乖乖,运行计划一下多了两个处理(Key Lookup, Nested Loop):



Key Lookup 是一个繁重的处理,我们能够使用keyword WITH 来指定使用 Clustered Index,以此回避Key Lookup。

SELECT
ModifiedDate,
SalesOrderID,
SalesOrderDetailID,
ProductID,
UnitPrice
FROM SalesOrdDetailDemo WITH(INDEX=idx_SalesDetail_SalesOrderlID)
WHERE ModifiedDate='2005-07-01 00:00:00.000'
GO

运行计划应声而变成为一个 Clustered Index Scan:

前文提过 Scan 似乎也不是一个非常好的处理,那么矮子里拔高个,使用 SET STATISTICS IO ON 来比較一下:

SET STATISTICS IO ON
GO SELECT
ModifiedDate,
SalesOrderID,
SalesOrderDetailID,
ProductID,
UnitPrice
FROM SalesOrdDetailDemo
WHERE ModifiedDate='2005-07-01 00:00:00.000'
GO SELECT
ModifiedDate,
SalesOrderID,
SalesOrderDetailID,
ProductID,
UnitPrice
FROM SalesOrdDetailDemo WITH(INDEX=idx_SalesDetail_SalesOrderlID)
WHERE ModifiedDate='2005-07-01 00:00:00.000'
GO SELECT
ModifiedDate,
SalesOrderID,
SalesOrderDetailID,
ProductID,
UnitPrice
FROM SalesOrdDetailDemo WITH(INDEX=idx_non_clust_SalesOrdDetailDemo_ModifiedDate)
WHERE ModifiedDate='2005-07-01 00:00:00.000'
GO



比較下来,採用了 clustered index 的查询表现最差,另外 SET STATISTICS IO 输出的数据中clustered index 的查询在 logical reads 上花费了很多其它的时间。

看起来採用 non-clustered index + Key Lookup 运行计划表现还不错,但假设能回避 Key Lookup 就完美了,我们来把 non-clustered index 改动一下,用 INCLUDE keyword在索引中包括其它的字段:

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

再次运行之前的查询:

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

这下完美了,由于我们的查询字段都包括在索引中,所以运行计划终于被优化为 Index Seek。

SQL Server 性能调优 之运行计划(Execution Plan)调优的更多相关文章

  1. (转)SQL Server 性能调优(cpu)

    摘自:http://www.cnblogs.com/Amaranthus/archive/2012/03/07/2383551.html 研究cpu压力工具 perfom SQL跟踪 性能视图 cpu ...

  2. SQL Server 性能调优培训引言

    原文:SQL Server 性能调优培训引言 大家好,这是我在博客园写的第一篇博文,之所以要开这个博客,是我对MS SQL技术学习的一个兴趣记录. 作为计算机专业毕业的人,自己对技术的掌握总是觉得很肤 ...

  3. sql server性能调优

    转自:https://www.cnblogs.com/woodytu/tag/%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98%E5%9F%B9%E8%AE%AD/defaul ...

  4. [转]SQL Server 性能调优(cpu)

      研究cpu压力工具 perfom SQL跟踪 性能视图 cpu相关的wait event Signal wait time SOS_SCHEDULER_YIELD等待 CXPACKET等待 CME ...

  5. sql server 性能调优 资源等待之内存瓶颈的三种等待类型

    原文:sql server 性能调优 资源等待之内存瓶颈的三种等待类型 一.概述 这篇介绍Stolen内存相关的主要三种等待类型以及对应的waittype编号,CMEMTHREAD(0x00B9),S ...

  6. sql server 性能调优之 资源等待 LCk

    一.  概述 这次介绍实例级别资源等待LCK类型锁的等待时间,关于LCK锁的介绍可参考 “sql server 锁与事务拨云见日”.下面还是使用sys.dm_os_wait_stats 来查看,并找出 ...

  7. sql server 性能调优之 CPU消耗最大资源分析1 (自sqlserver服务启动以后)

    一. 概述 上次在介绍性能调优中讲到了I/O的开销查看及维护,这次介绍CPU的开销及维护, 在调优方面是可以从多个维度去发现问题如I/O,CPU,  内存,锁等,不管从哪个维度去解决,都能达到调优的效 ...

  8. [转]SQL Server 性能调优(io)

      目录 诊断磁盘io问题 常见的磁盘问题 容量替代了性能 负载隔离配置有问题 分区对齐配置有问题 总结 关于io这一块,前面的东西如磁盘大小,磁盘带宽,随机读取写入,顺序读取写入,raid选择,DA ...

  9. CPU开销sql server 性能调优

    sql server 性能调优 CPU开销分析 一. 概述 上次在介绍性能调优中讲到了I/O的开销查看及维护,这次介绍CPU的开销及维护, 在调优方面是可以从多个维度去发现问题如I/O,CPU, 内存 ...

  10. 【目录】sql server 性能调优

    随笔分类 - sql server 性能调优 sql server 性能调优之 资源等待之网络I/O 摘要: 一.概述 与网络I/O相关的等待的主要是ASYNC_NETWORK_IO,是指当sql s ...

随机推荐

  1. HTTP简单的解析协议

    1.HTTP定义的协议 官方的定义:        WWW这是Internet作为传输介质的应用.WWW主变速器单元是在线Web网页.WWW它正在给客户/server计算模型,由Web浏览器Webse ...

  2. Androids含文档erver结束(工具包 Httputils)两

    在同server在...的基础上,本文client还登录界面 Andriod简单http get请求基础上,用户注冊后跳转到下载界面,本文下载界面仅仅有两个View,一个是textView显示注冊后u ...

  3. Google大数据三篇著名论文----中文版

    Google File System中文版 Google Bigtable中文版      Google MapReduce中文版

  4. 离robots.txt启动网络爬虫之旅

    要成为一个网络爬虫或搜索引擎(在这里,共同蜘蛛)它不会陌生,在搜索引擎爬虫的第一个文件或者访问该网站上浏览robots.txt该.robots.txt文件讲述了蜘蛛server哪些文件要观看正在. 当 ...

  5. UNIX网络编程卷1 server编程范式0 迭代server

    本文senlie原版的.转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.迭代 TCP server总是在全然处理某个客户的请求后才转向下一个客户. 2.从进程控 ...

  6. SpringAccess数据库(oracle)构造

    陈科朝:http://blog.csdn.net/u013474104/article/details/44279309 ================ 1.spring 对数据库訪问的支持 当我们 ...

  7. EXCEL Pivot table manipulate

    Add filter For the Demo time,I would like to filter out the products which not in Red and Black colo ...

  8. Ajax跨域请求数据实例(JSOPN方式)

    今天在做取消申请的时候遇到了一个跨域ajax提交的问题. 情景是: 系统A是asp.net的站点,其中包括一个取消申请的接口(get方式通过参数提交到系统的某一个页面,然后返回提交成功或失败) 系统B ...

  9. 使用 CodeIgniter 框架快速开发 PHP 应用(一)

    原文:使用 CodeIgniter 框架快速开发 PHP 应用(一) 对 CodeIgniter 的介绍大多数PHPer都想写出运行状态良好的应用程序,而且希望尽可能做得简单且不费事.这篇文章是有关 ...

  10. ABP依赖注入

    ABP依赖注入 点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之6.ABP依赖注入 ABP是“ASP.NET Boilerplate Project (ASP.N ...