设为首页 收藏本站
查看: 1088|回复: 0

[经验分享] SQL Server 性能调优 之执行计划(Execution Plan)调优

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2014-7-9 09:59:10 | 显示全部楼层 |阅读模式
执行计划中的三种 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  

Center.jpg
图中的红圈标出了table scan,并且执行计划也智能得建议建立索引。我们先尝试在SalesOrdHeader 表上建立一个索引:
  • CREATE UNIQUE CLUSTERED INDEX idx_salesorderheaderdemo_SalesOrderID ON SalesOrdHeaderDemo (SalesOrderID)  
  • GO  


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

table scan 变为了 Index Scan,继续给另一张表也加上索引:
  • CREATE UNIQUE CLUSTERED INDEX idx_SalesDetail_SalesOrderlID ON SalesOrdDetailDemo (SalesOrderID,SalesOrderDetailID)  
  • GO  


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

虽然不能说 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为了后续的示例,我们先在同一张表上建立两个不同的索引:
  • 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 处理。
Center.jpg
我们改造一下查询语句,SELECT 中多加两个字段:
  • SELECT  
  •   ModifiedDate,  
  •   SalesOrderID,  
  •   SalesOrderDetailID  
  • FROM SalesOrdDetailDemo  
  • WHERE ModifiedDate='2005-07-01 00:00:00.000'  
  • GO  

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

上面选出的字段不是属于 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):
Center.jpg
Key Lookup 是一个繁重的处理,我们可以使用关键字 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: Center.jpg

前文提过 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  

Center.jpg
比较下来,采用了 clustered index 的查询表现最差,另外 SET STATISTICS IO 输出的数据中clustered index 的查询在 logical reads 上花费了更多的时间。看起来采用 non-clustered index + Key Lookup 执行计划表现还不错,但如果能回避 Key Lookup 就完美了,我们来把 non-clustered index 修改一下,用 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  

再次执行之前的查询:
  • SELECT  
  •   ModifiedDate,  
  •   SalesOrderID,  
  •   SalesOrderDetailID,  
  •   ProductID,  
  •   UnitPrice  
  • FROM SalesOrdDetailDemo  
  • WHERE ModifiedDate='2005-07-01 00:00:00.000'  
  • GO  

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




运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-21878-1-1.html 上篇帖子: 关于sqlserver2012重启后ID自增1000的问题解决方案 下篇帖子: SQL Server 性能调优2 之索引(Index)的建立 计划
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表