SQL Server 性能调优 之执行计划(Execution Plan)调优
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 关键字对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 是一个繁重的处理,我们可以使用关键字 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 关键字在索引中包含其他的字段:
- 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)调优的更多相关文章
- SQL Server INSET/UPDATE/DELETE的执行计划
DML操作符包括增删改查等操作方式. insert into Person.Address (AddressLine1, AddressLine2, City, StateProvinceID, Po ...
- sql server 执行计划(execution plan)介绍
大纲:目的介绍sql server 中执行计划的大致使用,当遇到查询性能瓶颈时,可以发挥用处,而且带有比较详细的学习文档和计划,阅读者可以按照我计划进行,从而达到对执行计划一个比较系统的学习. 什么是 ...
- SQL Server如何查看存储过程的执行计划
有时候,我们需要查看存储过程的执行计划,那么我们有什么方式获取存储过程的历史执行计划或当前的执行计划呢? 下面总结一下获取存储过程的执行计划的方法. 1:我们可以通过下面脚本查看存储过程的执行计划,但 ...
- SQL Server 性能优化详解
故事开篇:你和你的团队经过不懈努力,终于使网站成功上线,刚开始时,注册用户较少,网站性能表现不错,但随着注册用户的增多,访问速度开始变慢,一些用户开始发来邮件表示抗议,事情变得越来越糟,为了留住用户, ...
- (转)SQL Server 性能调优(cpu)
摘自:http://www.cnblogs.com/Amaranthus/archive/2012/03/07/2383551.html 研究cpu压力工具 perfom SQL跟踪 性能视图 cpu ...
- SQL Server 性能调优培训引言
原文:SQL Server 性能调优培训引言 大家好,这是我在博客园写的第一篇博文,之所以要开这个博客,是我对MS SQL技术学习的一个兴趣记录. 作为计算机专业毕业的人,自己对技术的掌握总是觉得很肤 ...
- 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 ...
- [转]SQL Server 性能调优(cpu)
研究cpu压力工具 perfom SQL跟踪 性能视图 cpu相关的wait event Signal wait time SOS_SCHEDULER_YIELD等待 CXPACKET等待 CME ...
- sql server 性能调优 资源等待之内存瓶颈的三种等待类型
原文:sql server 性能调优 资源等待之内存瓶颈的三种等待类型 一.概述 这篇介绍Stolen内存相关的主要三种等待类型以及对应的waittype编号,CMEMTHREAD(0x00B9),S ...
- sql server 性能调优之 资源等待 LCk
一. 概述 这次介绍实例级别资源等待LCK类型锁的等待时间,关于LCK锁的介绍可参考 “sql server 锁与事务拨云见日”.下面还是使用sys.dm_os_wait_stats 来查看,并找出 ...
随机推荐
- 『ACM C++』HDU杭电OJ | 1425 - sort (排序函数的特殊应用)
今天真的是累哭了,周一课从早八点半一直上到晚九点半,整个人要虚脱的感觉,因为时间不太够鸭所以就回头看看找了一些比较有知识点的题来总结总结分析一下,明天有空了就开始继续打题,嘻嘻嘻. 今日兴趣电影: & ...
- POJ 1066--Treasure Hunt(判断线段相交)
Treasure Hunt Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 7857 Accepted: 3247 Des ...
- Python学习——01Linux基础之常用基本命令
做Linux要知道两件事: 首先知道自己处在什么位置(桌面……) 区分大小写 pwd:查看当前所在目录 “/”代表:根目录 Cd: cd( ...
- ECSHOP快递单号查询插件圆通V8.2专版
本ECSHOP快递物流单号跟踪插件提供国内外近2000家快递物流订单单号查询服务例如申通快递.顺丰快递.圆通快递.EMS快递.汇通快递.宅急送快递.德邦物流.百世快递.汇通快递.中通快递.天天快递等知 ...
- laravel -- 路由
基本路由 Route::get('/get',function (){ return "this is get"; }); Route::post('/post',function ...
- 响应式布局--设置rem自适应
//designWidth:设计稿的实际宽度值,需要根据实际设置 //maxWidth:制作稿的最大宽度值,需要根据实际设置 //这段js的最后面有两个参数记得要设置,一个为设计稿实际宽度,一个为制作 ...
- ZooKeeper(3)-内部原理
一. 节点类型 二. Stat结构体 1)czxid-创建节点的事务zxid 每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID. 事务ID是ZooKee ...
- WordPress4.9 最新版本网站安全漏洞详情与修复
wordpress 目前互联网的市场占有率较高,许多站长以及建站公司都在使用这套开源的博客建站系统来设计网站,wordpress的优化以及html静态化,深受google以及搜索引擎的喜欢,全世界大约 ...
- 016---Django的ModelForm
对于forms组件虽然可以帮我们渲染html页面,也可以做校验,但是,保存到数据库要取各字段的值,还要手动保存.所以引入了一个新的组件 这是一个神奇的组件,通过名字我们可以看出来,这个组件的功能就是把 ...
- Educational Codeforces Round 47 (Rated for Div. 2) :E. Intercity Travelling
题目链接:http://codeforces.com/contest/1009/problem/E 解题心得: 一个比较简单的组合数学,还需要找一些规律,自己把方向想得差不多了但是硬是找不到规律,还是 ...