SQL SERVER 2012 执行计划走嵌套循环导致性能问题的案例
开发人员遇到一个及其诡异的的SQL性能问题,这段完整SQL语句如下所示:
- declare @UserId INT
- declare @PSANo VARCHAR(200)
- declare @ShipMode VARCHAR(10)
- declare @CY_FLAG VARCHAR(1)
- declare @PO VARCHAR(20)
- declare @BuyerName VARCHAR(100)
- declare @Destination VARCHAR(1)
- declare @FinalDestination VARCHAR(40)
- declare @Factory VARCHAR(10)
- declare @NoticeDateStart DATETIME
- declare @NoticeDateEnd DATETIME
- declare @EELForwarder VARCHAR(100)
- declare @SortExpression VARCHAR(100)
- declare @RowIndex INT
- declare @PageSize INT
- declare @ExistNoticeKey varchar(200)
- DECLARE @NULLDATE DATETIME
- SET @NULLDATE=GETDATE()
- set @UserId=39
- set @PSANo=''
- set @ShipMode=''
- set @CY_FLAG=''
- set @PO=N''
- set @BuyerName=N''
- set @Destination=N''
- set @FinalDestination=N''
- set @Factory=''
- set @EELForwarder=N''
- set @SortExpression=''
- set @RowIndex=0
- set @PageSize=10
- set @ExistNoticeKey=''
- DECLARE @CountSql NVARCHAR(max)
- DECLARE @DataSql NVARCHAR(max)
- declare @next int
- declare @Where_PSANo varchar(400)
- declare @Index_PSANo varchar(40)
- declare @Where_ExcludeNotcekey varchar(400)
- set @Where_PSANo=''
- SET NOCOUNT ON;
- set @next=1
- while @next<=dbo.Get_StrArrayLength(@PSANo,',')
- begin
- set @Index_PSANo = dbo.Get_StrArrayStrOfIndex(@PSANo,',',@next)
- set @Where_PSANo = @Where_PSANo + ' Or notice.PSA_NO LIKE ''%'+@Index_PSANo+'%'''
- set @next=@next+1
- end
- set @Where_ExcludeNotcekey=''
- if @ExistNoticeKey!=''
- begin
- set @Where_ExcludeNotcekey=' or notice.NOTICE_KEY not in('+ @ExistNoticeKey+')';
- --select @Where_ExcludePSANo
- --print 'OK'
- end
- SELECT SUM(ISNULL(FactQty,0)) AS FactQty, NOTICE_KEY INTO #TEMP
- FROM
- (
- SELECT A.NOTICE_KEY,SUM(ISNULL(A.FactQty,0)) FactQty FROM IES.InvoiceFourLine A GROUP BY A.NOTICE_KEY
- UNION ALL
- SELECT A.NoticeKey AS NOTICE_KEY,SUM(ISNULL(A.FactQty,0)) FactQty FROM IES.InvoiceThreeByrFwdChargeLine A GROUP BY A.NoticeKey
- ) T GROUP BY NOTICE_KEY
- SELECT COUNT(*)
- FROM IES.ExportNotice notice --WITH (INDEX(PK_EXPORTNOTICE))
- LEFT JOIN #TEMP t ON notice.NOTICE_KEY = T.NOTICE_KEY
- WHERE
- notice.FACTORY_CD IN(SELECT SiteId FROM DCL.SecurityUserSiteMapping WHERE UserId=39)
- AND (ISNULL(notice.FACT_EXPORT_QTY,0)-ISNULL(T.FactQty,0))>0
- AND (ISNULL(@PSANo,'')='' Or notice.PSA_NO LIKE '%%')
- AND (ISNULL(@ExistNoticeKey,'')='' )
- AND (ISNULL(@ShipMode,'')='' OR notice.SHIP_MODE_CD=@ShipMode)
- AND (ISNULL(@CY_FLAG,'')='' OR notice.CY_FLAG=@CY_FLAG)
- AND (ISNULL(@PO,'')='' OR notice.BUYER_PO_NO LIKE '%'+@PO+'%')
- AND (ISNULL(@BuyerName,'')='' OR notice.NAME LIKE '%'+@BuyerName+'%')
- AND (ISNULL(@Destination,'')='' OR notice.SZ=@Destination)
- AND (ISNULL(@FinalDestination,'')='' OR notice.FINAL_DESTINATION LIKE '%'+@FinalDestination+'%')
- AND (ISNULL(@Factory,'')='' OR notice.FACTORY_CD=@Factory)
- AND (ISNULL(@EELForwarder,'')='' OR notice.EEL_FORWARDER=@EELForwarder)
- AND (ISNULL(@NoticeDateStart,'2000-01-01')='2000-01-01')
- ---AND ( ISNULL(@NoticeDateEnd,'1999-01-01')='1999-01-01')
- DROP TABLE #TEMP
案例的环境为SQL SERVER 2012 Standard Edition (64-bit),具体版本号为11.0.5058.0 ,另外表IES.ExportNotice的数据记录为2万多。表IES.InvoiceThreeByrFwdChargeLine的记录数为1万多,表IES.InvoiceFourLine的记录只有区区几十条。临时表 #TEMP的记录为1万多条。
执行上面SQL语句一般一秒以内完成。但是这段SQL如果将最后注释的条件加上(也就是最后注释的语句取消注释)
- SELECT COUNT(*)
- FROM IES.ExportNotice notice --WITH (INDEX(PK_EXPORTNOTICE))
- LEFT JOIN #TEMP t ON notice.NOTICE_KEY = T.NOTICE_KEY
- WHERE
- notice.FACTORY_CD IN(SELECT SiteId FROM DCL.SecurityUserSiteMapping WHERE UserId=39)
- AND (ISNULL(notice.FACT_EXPORT_QTY,0)-ISNULL(T.FactQty,0))>0
- AND (ISNULL(@PSANo,'')='' Or notice.PSA_NO LIKE '%%')
- AND (ISNULL(@ExistNoticeKey,'')='' )
- AND (ISNULL(@ShipMode,'')='' OR notice.SHIP_MODE_CD=@ShipMode)
- AND (ISNULL(@CY_FLAG,'')='' OR notice.CY_FLAG=@CY_FLAG)
- AND (ISNULL(@PO,'')='' OR notice.BUYER_PO_NO LIKE '%'+@PO+'%')
- AND (ISNULL(@BuyerName,'')='' OR notice.NAME LIKE '%'+@BuyerName+'%')
- AND (ISNULL(@Destination,'')='' OR notice.SZ=@Destination)
- AND (ISNULL(@FinalDestination,'')='' OR notice.FINAL_DESTINATION LIKE '%'+@FinalDestination+'%')
- AND (ISNULL(@Factory,'')='' OR notice.FACTORY_CD=@Factory)
- AND (ISNULL(@EELForwarder,'')='' OR notice.EEL_FORWARDER=@EELForwarder)
- AND (ISNULL(@NoticeDateStart,'2000-01-01')='2000-01-01')
- AND ( ISNULL(@NoticeDateEnd,'1999-01-01')='1999-01-01')
然后执行时发现SQL慢得令人发指,非常的不可以思议。 如果按照我们理解,这个条件( ISNULL(@NoticeDateEnd,'1999-01-01')='1999-01-01') 仅仅相当于一个 1=1 或1=0的条件,怎么会有如此大的性能差距呢? 查看执行计划后,发现加上这样一个条件后,执行计划完全不同了。
我姑且将执行性能较好的SQL的执行计划叫做Plan A,执行性能很差的SQL的执行计划叫做Plan B
Plan A
Plan B
如上所示,Plan B 看似开销都耗费在键查找那一块,但是如果查看具体信息(如下所示),并无特别地方。
于是我使用HINT,强制在表IES.ExportNotice上走索引PK_EXPORTNOTICE,结果发现执行时,执行速度依然慢的令人发指。我觉得执行计划有些问题,Cost可能并不正确。
- SELECT COUNT(*)
- FROM IES.ExportNotice notice WITH (INDEX(PK_EXPORTNOTICE))
- LEFT JOIN #TEMP t ON notice.NOTICE_KEY = T.NOTICE_KEY
- WHERE
- notice.FACTORY_CD IN(SELECT SiteId FROM DCL.SecurityUserSiteMapping WHERE UserId=39)
- AND (ISNULL(notice.FACT_EXPORT_QTY,0)-ISNULL(T.FactQty,0))>0
- AND (ISNULL(@PSANo,'')='' Or notice.PSA_NO LIKE '%%')
- AND (ISNULL(@ExistNoticeKey,'')='' )
- AND (ISNULL(@ShipMode,'')='' OR notice.SHIP_MODE_CD=@ShipMode)
- AND (ISNULL(@CY_FLAG,'')='' OR notice.CY_FLAG=@CY_FLAG)
- AND (ISNULL(@PO,'')='' OR notice.BUYER_PO_NO LIKE '%'+@PO+'%')
- AND (ISNULL(@BuyerName,'')='' OR notice.NAME LIKE '%'+@BuyerName+'%')
- AND (ISNULL(@Destination,'')='' OR notice.SZ=@Destination)
- AND (ISNULL(@FinalDestination,'')='' OR notice.FINAL_DESTINATION LIKE '%'+@FinalDestination+'%')
- AND (ISNULL(@Factory,'')='' OR notice.FACTORY_CD=@Factory)
- AND (ISNULL(@EELForwarder,'')='' OR notice.EEL_FORWARDER=@EELForwarder)
- AND (ISNULL(@NoticeDateStart,'2000-01-01')='2000-01-01')
- AND ( ISNULL(@NoticeDateEnd,'1999-01-01')='1999-01-01')
于是我将怀疑的地方转移到表连接方式,使用Table HINT,强制下面SQL语句走HASH JOIN,结果SQL一秒钟执行完成。
- SELECT COUNT(*)
- FROM IES.ExportNotice notice
- LEFT HASH JOIN #TEMP t ON notice.NOTICE_KEY = T.NOTICE_KEY
- WHERE
- notice.FACTORY_CD IN(SELECT SiteId FROM DCL.SecurityUserSiteMapping WHERE UserId=39)
- AND (ISNULL(notice.FACT_EXPORT_QTY,0)-ISNULL(T.FactQty,0))>0
- AND (ISNULL(@PSANo,'')='' Or notice.PSA_NO LIKE '%%')
- AND (ISNULL(@ExistNoticeKey,'')='' )
- AND (ISNULL(@ShipMode,'')='' OR notice.SHIP_MODE_CD=@ShipMode)
- AND (ISNULL(@CY_FLAG,'')='' OR notice.CY_FLAG=@CY_FLAG)
- AND (ISNULL(@PO,'')='' OR notice.BUYER_PO_NO LIKE '%'+@PO+'%')
- AND (ISNULL(@BuyerName,'')='' OR notice.NAME LIKE '%'+@BuyerName+'%')
- AND (ISNULL(@Destination,'')='' OR notice.SZ=@Destination)
- AND (ISNULL(@FinalDestination,'')='' OR notice.FINAL_DESTINATION LIKE '%'+@FinalDestination+'%')
- AND (ISNULL(@Factory,'')='' OR notice.FACTORY_CD=@Factory)
- AND (ISNULL(@EELForwarder,'')='' OR notice.EEL_FORWARDER=@EELForwarder)
- AND (ISNULL(@NoticeDateStart,'2000-01-01')='2000-01-01')
- AND ( ISNULL(@NoticeDateEnd,'1999-01-01')='1999-01-01')
虽然解决了问题,但是我隐隐觉得这应该是SQL SERVER优化器的某些Bug才导致出现这种特殊的情况。而且执行计划的Cost也完全不准确。让人有点匪夷所思。
SQL SERVER 2012 执行计划走嵌套循环导致性能问题的案例的更多相关文章
- SQL Server 优化-执行计划
对于SQL Server的优化来说,优化查询可能是很常见的事情.由于数据库的优化,本身也是一个涉及面比较的广的话题, 因此本文只谈优化查询时如何看懂SQL Server查询计划.毕竟我对SQL Ser ...
- 了解Sql Server的执行计划
前一篇总结了Sql Server Profiler,它主要用来监控数据库,并跟踪生成的sql语句.但是只拿到生成的sql语句没有什么用,我们可以利用这些sql语句,然后结合执行计划来分析sql语句的性 ...
- SQL SERVER 2012数据库:开启防火墙导致外部无法连接数据库解决办法
SQL SERVER 2012数据库:开启防火墙导致外部无法连接数据库解决办法 将以下代码存为OpenSqlServerPort.bat文件: netsh advfirewall firewall a ...
- SQL Server实际执行计划COST"欺骗"案例
有个系统,昨天Support人员发布了相关升级脚本后,今天发现系统中有个功能不能正常使用了,直接报超时了(Timeout expired)的错误.定位到相关相关存储过程后,然后在优化分析的过程中,又遇 ...
- 程序员眼中的 SQL Server-执行计划教会我如何创建索引?
先说点废话 以前有 DBA 在身边的时候,从来不曾考虑过数据库性能的问题,但是,当一个应用程序从头到脚都由自己完成,而且数据库面对的是接近百万的数据,看着一个页面加载速度像乌龟一样,自己心里真是有种挫 ...
- SQL Server-执行计划教会我如何创建索引
先说点废话 以前有 DBA 在身边的时候,从来不曾考虑过数据库性能的问题,但是,当一个应用程序从头到脚都由自己完成,而且数据库面对的是接近百万的数据,看着一个页面加载速度像乌龟一样,自己心里真是有种挫 ...
- Sql Server中执行计划的缓存机制
Sql查询过程 当执行一个Sql语句或者存储过程时, Sql Server的大致过程是 1. 对查询语句进行分析,将其生成逻辑单元,并进行基本的语法检查 2. 生成查询树(会将查询语句中所有操作转换为 ...
- Win8.1 IIS6 SQL SERVER 2012 执行 SqlServices.InstallSessionState 出错
新装了WIN8.1,感觉很不错. 新建了第一个站点是,在执行 SqlServices.InstallSessionState("localhost", null, SessionS ...
- SQL Server控制执行计划
为了提高性能,可以使用提示(hints)特性,包含以下三类: 查询提示:(query hints)告知优化器在整个查询过程中都应用某个提示 关联提示:(join hints)告知优化器在查询的特定部分 ...
随机推荐
- Generator库co4.6使用及源码分析
原文链接 http://www.cnblogs.com/ytu2010dt/p/6043947.html co4.x已经抛弃了原来thunk转而结合promise实现. 一:promise proms ...
- Windows Server 2008 下解析二级域名的方法
昨天去了客户那里部署网站,用的是客户那边的windows server 2008. 本文主要以总结问题点的形式来说. 问题1:本机的数据库是SQL SERVER 2008R2,客户那边的数据库是SQL ...
- QTableWidget行选中/删除/添加行
1 均分各列 tableWidget->horizontalHeader()->setStretchLastSection(true); //就是这个地方 tableWidget-> ...
- package.json for npm中依赖外部组件时常用的版本符号含义
package.json中会有dependencies定义了项目依赖的外部组件,这些外部组件的依赖都是带有版本符号以表示被依赖组件的版本范围. { "dependencies" : ...
- 【Win10开发】Toast通知——前台激活
上篇文章我们将了大体的Toast通知的模板及实例展示,那么,这篇文章就来讲讲Toast的前台激活. 首先是xaml界面,很简单,我们放一个Button和TextBlock,TextBlock用来显示T ...
- 【NopCommerce源码架构学习-一】--初识高性能的开源商城系统cms
很多人都说通过阅读.学习大神们高质量的代码是提高自己技术能力最快的方式之一.我觉得通过阅读NopCommerce的源码,可以从中学习很多企业系统.软件开发的规范和一些新的技术.技巧,可以快速地提高我们 ...
- 【nodejs笔记——小知识点汇总】
1. ejs标签: <% %> , <%- %> , <%= %>的区别 ejs的标签分为三种: (1)<% code %> javasc ...
- 当shiro不进入自定义realm的权限认证方法时
需要加入下面的一个bean @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorA ...
- 【linux草鞋应用编程系列】_2_ 环境变量和进程控制
一. 环境变量 应用程序在执行的时候,可能需要获取系统的环境变量,从而执行一些相应的操作. 在linux中有两种方法获取环境变量,分述如下. 1.通过main函数的参数获取环境变量 ...
- git 常用指令
下载项目 git clone https://git.oschina.net/jianqingwang/jianblog.git 注意,clone跟的是项目地址 查看分支(也就是版本) git bra ...