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

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

  1. SELECT
  2. sh.*
  3. FROM
  4. SalesOrdHeaderDemo AS sh
  5. JOIN
  6. SalesOrdDetailDemo AS sd
  7. ON
  8. sh.SalesOrderID=sd.SalesOrderID
  9. GO

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

  1. CREATE UNIQUE CLUSTERED INDEX idx_salesorderheaderdemo_SalesOrderID ON SalesOrdHeaderDemo (SalesOrderID)
  2. GO
  3. CREATE UNIQUE CLUSTERED INDEX idx_SalesDetail_SalesOrderlID ON SalesOrdDetailDemo (SalesOrderID,SalesOrderDetailID)
  4. GO

Nested Loop Join:在满足Merge
Join的基础上,如果某一边的数据较少,那么SQL Server
会把数据较少的那个作为外部循环,另一个作为内部循环来完成Join处理。继续前面的例子为查询语句加上WHERE语句来减少 Join
一边的数据量,执行计划显示为Nested Loop Join。

  1. SELECT
  2. sh.*
  3. FROM
  4. SalesOrdHeaderDemo AS sh
  5. JOIN
  6. SalesOrdDetailDemo AS sd
  7. ON
  8. sh.SalesOrderID=sd.SalesOrderID
  9. WHERE
  10. sh.SalesOrderID=43659

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

在许多场合我们需要在一张包含许多数据的表中提取出一小部分数据,此时应当避免Scan,因为扫描处理会遍历每一行,这是相当耗时耗力的。下面我们来看一个例子:

  1. SELECT
  2. sh.SalesOrderID
  3. FROM
  4. SalesOrdHeaderDemo AS sh
  5. JOIN
  6. SalesOrdDetailDemo AS sd
  7. ON
  8. sh.SalesOrderID=sd.SalesOrderID
  9. WHERE
  10. sh.OrderDate='2005-07-01 00:00:00.000'
  11. GO

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

  1. CREATE UNIQUE CLUSTERED INDEX idx_salesorderheaderdemo_SalesOrderID ON SalesOrdHeaderDemo (SalesOrderID)
  2. GO

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

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

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

执行计划发生以下的变化:

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

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

执行计划中的 Key Lookup

为了后续的示例,我们先在同一张表上建立两个不同的索引:

  1. CREATE UNIQUE CLUSTERED INDEX idx_SalesDetail_SalesOrderlID ON SalesOrdDetailDemo (SalesOrderID,SalesOrderDetailID)
  2. GO
  3. CREATE NONCLUSTERED INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo(ModifiedDate)
  4. GO

执行以下的查询:

  1. SELECT
  2. ModifiedDate
  3. FROM SalesOrdDetailDemo
  4. WHERE ModifiedDate='2005-07-01 00:00:00.000'
  5. GO

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

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

  1. SELECT
  2. ModifiedDate,
  3. SalesOrderID,
  4. SalesOrderDetailID
  5. FROM SalesOrdDetailDemo
  6. WHERE ModifiedDate='2005-07-01 00:00:00.000'
  7. GO

执行计划如下图,基本没变:

上面选出的字段不是属于 Non-Clustered Index 就是属于 Clustered Index,如果再增加几个其他的字段呢?

  1. SELECT
  2. ModifiedDate,
  3. SalesOrderID,
  4. SalesOrderDetailID,
  5. ProductID,
  6. UnitPrice
  7. FROM SalesOrdDetailDemo
  8. WHERE ModifiedDate='2005-07-01 00:00:00.000'
  9. GO

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


Key Lookup 是一个繁重的处理,我们可以使用关键字 WITH 来指定使用 Clustered Index,以此回避Key Lookup。

  1. SELECT
  2. ModifiedDate,
  3. SalesOrderID,
  4. SalesOrderDetailID,
  5. ProductID,
  6. UnitPrice
  7. FROM SalesOrdDetailDemo WITH(INDEX=idx_SalesDetail_SalesOrderlID)
  8. WHERE ModifiedDate='2005-07-01 00:00:00.000'
  9. GO

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

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

  1. SET STATISTICS IO ON
  2. GO
  3. SELECT
  4. ModifiedDate,
  5. SalesOrderID,
  6. SalesOrderDetailID,
  7. ProductID,
  8. UnitPrice
  9. FROM SalesOrdDetailDemo
  10. WHERE ModifiedDate='2005-07-01 00:00:00.000'
  11. GO
  12. SELECT
  13. ModifiedDate,
  14. SalesOrderID,
  15. SalesOrderDetailID,
  16. ProductID,
  17. UnitPrice
  18. FROM SalesOrdDetailDemo WITH(INDEX=idx_SalesDetail_SalesOrderlID)
  19. WHERE ModifiedDate='2005-07-01 00:00:00.000'
  20. GO
  21. SELECT
  22. ModifiedDate,
  23. SalesOrderID,
  24. SalesOrderDetailID,
  25. ProductID,
  26. UnitPrice
  27. FROM SalesOrdDetailDemo WITH(INDEX=idx_non_clust_SalesOrdDetailDemo_ModifiedDate)
  28. WHERE ModifiedDate='2005-07-01 00:00:00.000'
  29. GO


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

看起来采用 non-clustered index + Key Lookup 执行计划表现还不错,但如果能回避 Key Lookup 就完美了,我们来把 non-clustered index 修改一下,用 INCLUDE 关键字在索引中包含其他的字段:

  1. DROP INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo
  2. GO
  3. CREATE NONCLUSTERED INDEX idx_non_clust_SalesOrdDetailDemo_ModifiedDate ON SalesOrdDetailDemo(ModifiedDate)
  4. INCLUDE
  5. (
  6. ProductID,
  7. UnitPrice
  8. )
  9. GO
  10. -- 清下缓存,仅用于开发环境!
  11. DBCC FREEPROCCACHE
  12. DBCC DROPCLEANBUFFERS
  13. GO

再次执行之前的查询:

  1. SELECT
  2. ModifiedDate,
  3. SalesOrderID,
  4. SalesOrderDetailID,
  5. ProductID,
  6. UnitPrice
  7. FROM SalesOrdDetailDemo
  8. WHERE ModifiedDate='2005-07-01 00:00:00.000'
  9. GO

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

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

  1. SQL Server INSET/UPDATE/DELETE的执行计划

    DML操作符包括增删改查等操作方式. insert into Person.Address (AddressLine1, AddressLine2, City, StateProvinceID, Po ...

  2. sql server 执行计划(execution plan)介绍

    大纲:目的介绍sql server 中执行计划的大致使用,当遇到查询性能瓶颈时,可以发挥用处,而且带有比较详细的学习文档和计划,阅读者可以按照我计划进行,从而达到对执行计划一个比较系统的学习. 什么是 ...

  3. SQL Server如何查看存储过程的执行计划

    有时候,我们需要查看存储过程的执行计划,那么我们有什么方式获取存储过程的历史执行计划或当前的执行计划呢? 下面总结一下获取存储过程的执行计划的方法. 1:我们可以通过下面脚本查看存储过程的执行计划,但 ...

  4. SQL Server 性能优化详解

    故事开篇:你和你的团队经过不懈努力,终于使网站成功上线,刚开始时,注册用户较少,网站性能表现不错,但随着注册用户的增多,访问速度开始变慢,一些用户开始发来邮件表示抗议,事情变得越来越糟,为了留住用户, ...

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

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

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

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

  7. 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 ...

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

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

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

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

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

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

随机推荐

  1. atan和atan2反正切计算

    typedef struct point { double x, y; }point; //给定两个点 point a(x1,y1),b(x2,y2); 使用反三角函数atan求斜率,原型如下 flo ...

  2. 通过xshell在linux上安装solr4.10.3

    通过xshell在linux上安装solr4.10.3 0)下载linux下的安装包 1)通过xftp6上传到linux上 3)在xshell下依次执行 解压命令:tar xvfz solr.tgz( ...

  3. MySQL实现排名并查询指定用户排名功能,并列排名功能

    MySQL实现排名并查询指定用户排名功能,并列排名功能 表结构: CREATE TABLE test.testsort ( id int(11) NOT NULL AUTO_INCREMENT, ui ...

  4. MySQL 清除表空间碎片

    碎片产生的原因 (1)表的存储会出现碎片化,每当删除了一行内容,该段空间就会变为空白.被留空,而在一段时间内的大量删除操作,会使这种留空的空间变得比存储列表内容所使用的空间更大; (2)当执行插入操作 ...

  5. 【Js】JSON对象、JSON字符的使用总结

    JSON对象 / JSON字符串区别 抛出一个最常见的疑问:什么是“JSON对象”,什么是“JSON字符串”,它俩的区别是什么? 废话不多说,直接上代码. 1.JSON对象: // javascrip ...

  6. 毕业2年 Summary

    本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/135 看了下去年写毕业一周年总结的时间:2017-6-16,今天 ...

  7. 12、K最近邻算法(KNN算法)

    一.如何创建推荐系统? 找到与用户相似的其他用户,然后把其他用户喜欢的东西推荐给用户.这就是K最近邻算法的分类作用. 二.抽取特征 推荐系统最重要的工作是:将用户的特征抽取出来并转化为度量的数字,然后 ...

  8. wamp调用ICE中间件

    wamp调用ICE中间件 wamp 是集成开发包,我的wamp中的php 为5.3.10 ,经过3天艰苦奋战,终于在phpinfo()中看到了 ICE 出现了.. OK,最新的ice  为 3.5.1 ...

  9. IDEA中SVN的使用

    文件红色:表示文件没有添加到服务器 绿色:表示没有更新新的修改到服务器 普通黑色:表示和服务器同步 1.如何让修改的文件的父文件也变成蓝色(未提交的状态) 2.其中的1.6 format 1.7 fo ...

  10. 20145202马超 2006-2007-2 《Java程序设计》第3周学习总结

    20145202马超 2016-2017-2 <Java程序设计>第3周学习总结 教材学习内容总结 函数的重载:在同一个类中,允许存在一个以上的同名函数,只要他们的参数数目不同就可以.与返 ...