SQL SERVER 2012 执行计划走嵌套循环导致性能问题的案例
2015-07-04 11:48
447 查看
开发人员遇到一个及其诡异的的SQL性能问题,这段完整SQL语句如下所示:
案例的环境为SQLSERVER2012StandardEdition(64-bit),具体版本号为11.0.5058.0,另外表IES.ExportNotice的数据记录为2万多。表IES.InvoiceThreeByrFwdChargeLine的记录数为1万多,表IES.InvoiceFourLine的记录只有区区几十条。临时表#TEMP的记录为1万多条。
执行上面SQL语句一般一秒以内完成。但是这段SQL如果将最后注释的条件加上(也就是最后注释的语句取消注释)
然后执行时发现SQL慢得令人发指,非常的不可以思议。如果按照我们理解,这个条件(ISNULL(@NoticeDateEnd,'1999-01-01')='1999-01-01')仅仅相当于一个1=1或1=0的条件,怎么会有如此大的性能差距呢?查看执行计划后,发现加上这样一个条件后,执行计划完全不同了。
我姑且将执行性能较好的SQL的执行计划叫做PlanA,执行性能很差的SQL的执行计划叫做PlanB
PlanA
PlanB
如上所示,PlanB看似开销都耗费在键查找那一块,但是如果查看具体信息(如下所示),并无特别地方。
于是我使用HINT,强制在表IES.ExportNotice上走索引PK_EXPORTNOTICE,结果发现执行时,执行速度依然慢的令人发指。我觉得执行计划有些问题,Cost可能并不正确。
于是我将怀疑的地方转移到表连接方式,使用TableHINT,强制下面SQL语句走HASHJOIN,结果SQL一秒钟执行完成。
虽然解决了问题,但是我隐隐觉得这应该是SQLSERVER优化器的某些Bug才导致出现这种特殊的情况。而且执行计划的Cost也完全不准确。让人有点匪夷所思。
declare@UserIdINT
declare@PSANoVARCHAR(200)
declare@ShipModeVARCHAR(10)
declare@CY_FLAGVARCHAR(1)
declare@POVARCHAR(20)
declare@BuyerNameVARCHAR(100)
declare@DestinationVARCHAR(1)
declare@FinalDestinationVARCHAR(40)
declare@FactoryVARCHAR(10)
declare@NoticeDateStartDATETIME
declare@NoticeDateEndDATETIME
declare@EELForwarderVARCHAR(100)
declare@SortExpressionVARCHAR(100)
declare@RowIndexINT
declare@PageSizeINT
declare@ExistNoticeKeyvarchar(200)
DECLARE@NULLDATEDATETIME
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@CountSqlNVARCHAR(max)
DECLARE@DataSqlNVARCHAR(max)
declare@nextint
declare@Where_PSANovarchar(400)
declare@Index_PSANovarchar(40)
declare@Where_ExcludeNotcekeyvarchar(400)
set@Where_PSANo=''
SETNOCOUNTON;
set@next=1
while@next<=dbo.Get_StrArrayLength(@PSANo,',')
begin
set@Index_PSANo=dbo.Get_StrArrayStrOfIndex(@PSANo,',',@next)
set@Where_PSANo=@Where_PSANo+'Ornotice.PSA_NOLIKE''%'+@Index_PSANo+'%'''
set@next=@next+1
end
set@Where_ExcludeNotcekey=''
if@ExistNoticeKey!=''
begin
set@Where_ExcludeNotcekey='ornotice.NOTICE_KEYnotin('+@ExistNoticeKey+')';
--select@Where_ExcludePSANo
--print'OK'
end
SELECTSUM(ISNULL(FactQty,0))ASFactQty,NOTICE_KEYINTO#TEMP
FROM
(
SELECTA.NOTICE_KEY,SUM(ISNULL(A.FactQty,0))FactQtyFROMIES.InvoiceFourLineAGROUPBYA.NOTICE_KEY
UNIONALL
SELECTA.NoticeKeyASNOTICE_KEY,SUM(ISNULL(A.FactQty,0))FactQtyFROMIES.InvoiceThreeByrFwdChargeLineAGROUPBYA.NoticeKey
)TGROUPBYNOTICE_KEY
SELECTCOUNT(*)
FROMIES.ExportNoticenotice--WITH(INDEX(PK_EXPORTNOTICE))
LEFTJOIN#TEMPtONnotice.NOTICE_KEY=T.NOTICE_KEY
WHERE
notice.FACTORY_CDIN(SELECTSiteIdFROMDCL.SecurityUserSiteMappingWHEREUserId=39)
AND(ISNULL(notice.FACT_EXPORT_QTY,0)-ISNULL(T.FactQty,0))>0
AND(ISNULL(@PSANo,'')=''Ornotice.PSA_NOLIKE'%%')
AND(ISNULL(@ExistNoticeKey,'')='')
AND(ISNULL(@ShipMode,'')=''ORnotice.SHIP_MODE_CD=@ShipMode)
AND(ISNULL(@CY_FLAG,'')=''ORnotice.CY_FLAG=@CY_FLAG)
AND(ISNULL(@PO,'')=''ORnotice.BUYER_PO_NOLIKE'%'+@PO+'%')
AND(ISNULL(@BuyerName,'')=''ORnotice.NAMELIKE'%'+@BuyerName+'%')
AND(ISNULL(@Destination,'')=''ORnotice.SZ=@Destination)
AND(ISNULL(@FinalDestination,'')=''ORnotice.FINAL_DESTINATIONLIKE'%'+@FinalDestination+'%')
AND(ISNULL(@Factory,'')=''ORnotice.FACTORY_CD=@Factory)
AND(ISNULL(@EELForwarder,'')=''ORnotice.EEL_FORWARDER=@EELForwarder)
AND(ISNULL(@NoticeDateStart,'2000-01-01')='2000-01-01')
---AND(ISNULL(@NoticeDateEnd,'1999-01-01')='1999-01-01')
DROPTABLE#TEMP
案例的环境为SQLSERVER2012StandardEdition(64-bit),具体版本号为11.0.5058.0,另外表IES.ExportNotice的数据记录为2万多。表IES.InvoiceThreeByrFwdChargeLine的记录数为1万多,表IES.InvoiceFourLine的记录只有区区几十条。临时表#TEMP的记录为1万多条。
执行上面SQL语句一般一秒以内完成。但是这段SQL如果将最后注释的条件加上(也就是最后注释的语句取消注释)
SELECTCOUNT(*)
FROMIES.ExportNoticenotice--WITH(INDEX(PK_EXPORTNOTICE))
LEFTJOIN#TEMPtONnotice.NOTICE_KEY=T.NOTICE_KEY
WHERE
notice.FACTORY_CDIN(SELECTSiteIdFROMDCL.SecurityUserSiteMappingWHEREUserId=39)
AND(ISNULL(notice.FACT_EXPORT_QTY,0)-ISNULL(T.FactQty,0))>0
AND(ISNULL(@PSANo,'')=''Ornotice.PSA_NOLIKE'%%')
AND(ISNULL(@ExistNoticeKey,'')='')
AND(ISNULL(@ShipMode,'')=''ORnotice.SHIP_MODE_CD=@ShipMode)
AND(ISNULL(@CY_FLAG,'')=''ORnotice.CY_FLAG=@CY_FLAG)
AND(ISNULL(@PO,'')=''ORnotice.BUYER_PO_NOLIKE'%'+@PO+'%')
AND(ISNULL(@BuyerName,'')=''ORnotice.NAMELIKE'%'+@BuyerName+'%')
AND(ISNULL(@Destination,'')=''ORnotice.SZ=@Destination)
AND(ISNULL(@FinalDestination,'')=''ORnotice.FINAL_DESTINATIONLIKE'%'+@FinalDestination+'%')
AND(ISNULL(@Factory,'')=''ORnotice.FACTORY_CD=@Factory)
AND(ISNULL(@EELForwarder,'')=''ORnotice.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的执行计划叫做PlanA,执行性能很差的SQL的执行计划叫做PlanB
PlanA
PlanB
如上所示,PlanB看似开销都耗费在键查找那一块,但是如果查看具体信息(如下所示),并无特别地方。
于是我使用HINT,强制在表IES.ExportNotice上走索引PK_EXPORTNOTICE,结果发现执行时,执行速度依然慢的令人发指。我觉得执行计划有些问题,Cost可能并不正确。
SELECTCOUNT(*)
FROMIES.ExportNoticenoticeWITH(INDEX(PK_EXPORTNOTICE))
LEFTJOIN#TEMPtONnotice.NOTICE_KEY=T.NOTICE_KEY
WHERE
notice.FACTORY_CDIN(SELECTSiteIdFROMDCL.SecurityUserSiteMappingWHEREUserId=39)
AND(ISNULL(notice.FACT_EXPORT_QTY,0)-ISNULL(T.FactQty,0))>0
AND(ISNULL(@PSANo,'')=''Ornotice.PSA_NOLIKE'%%')
AND(ISNULL(@ExistNoticeKey,'')='')
AND(ISNULL(@ShipMode,'')=''ORnotice.SHIP_MODE_CD=@ShipMode)
AND(ISNULL(@CY_FLAG,'')=''ORnotice.CY_FLAG=@CY_FLAG)
AND(ISNULL(@PO,'')=''ORnotice.BUYER_PO_NOLIKE'%'+@PO+'%')
AND(ISNULL(@BuyerName,'')=''ORnotice.NAMELIKE'%'+@BuyerName+'%')
AND(ISNULL(@Destination,'')=''ORnotice.SZ=@Destination)
AND(ISNULL(@FinalDestination,'')=''ORnotice.FINAL_DESTINATIONLIKE'%'+@FinalDestination+'%')
AND(ISNULL(@Factory,'')=''ORnotice.FACTORY_CD=@Factory)
AND(ISNULL(@EELForwarder,'')=''ORnotice.EEL_FORWARDER=@EELForwarder)
AND(ISNULL(@NoticeDateStart,'2000-01-01')='2000-01-01')
AND(ISNULL(@NoticeDateEnd,'1999-01-01')='1999-01-01')
于是我将怀疑的地方转移到表连接方式,使用TableHINT,强制下面SQL语句走HASHJOIN,结果SQL一秒钟执行完成。
SELECTCOUNT(*)
FROMIES.ExportNoticenotice
LEFTHASHJOIN#TEMPtONnotice.NOTICE_KEY=T.NOTICE_KEY
WHERE
notice.FACTORY_CDIN(SELECTSiteIdFROMDCL.SecurityUserSiteMappingWHEREUserId=39)
AND(ISNULL(notice.FACT_EXPORT_QTY,0)-ISNULL(T.FactQty,0))>0
AND(ISNULL(@PSANo,'')=''Ornotice.PSA_NOLIKE'%%')
AND(ISNULL(@ExistNoticeKey,'')='')
AND(ISNULL(@ShipMode,'')=''ORnotice.SHIP_MODE_CD=@ShipMode)
AND(ISNULL(@CY_FLAG,'')=''ORnotice.CY_FLAG=@CY_FLAG)
AND(ISNULL(@PO,'')=''ORnotice.BUYER_PO_NOLIKE'%'+@PO+'%')
AND(ISNULL(@BuyerName,'')=''ORnotice.NAMELIKE'%'+@BuyerName+'%')
AND(ISNULL(@Destination,'')=''ORnotice.SZ=@Destination)
AND(ISNULL(@FinalDestination,'')=''ORnotice.FINAL_DESTINATIONLIKE'%'+@FinalDestination+'%')
AND(ISNULL(@Factory,'')=''ORnotice.FACTORY_CD=@Factory)
AND(ISNULL(@EELForwarder,'')=''ORnotice.EEL_FORWARDER=@EELForwarder)
AND(ISNULL(@NoticeDateStart,'2000-01-01')='2000-01-01')
AND(ISNULL(@NoticeDateEnd,'1999-01-01')='1999-01-01')
虽然解决了问题,但是我隐隐觉得这应该是SQLSERVER优化器的某些Bug才导致出现这种特殊的情况。而且执行计划的Cost也完全不准确。让人有点匪夷所思。
相关文章推荐
- Oracle 存储过程创建和执行入门实例
- mongodb 基本操作
- PL/SQL developer配置方法
- PyMongo系列一:操作MongoDB
- 基于nginx tomcat redis分布式web应用的session共享配置
- sql server 查询阻塞sql
- Mysql主从复制原理
- oracle忘记了sys,system 密码后怎么修改?
- centos 6.5 安装mongodb2.6
- postgresql教程
- centos 6.5 安装mongodb2.6
- Mysql主从配置
- Tip:自动平衡exchange 2010 DAG数据库分布的脚本
- MySQL详解(10)----------索引优化
- Tip:强制执行exchange DAG节点之间的数据库副本移动
- MySQL详解(9)----------索引详解
- innodb_flush_log_at_trx_commit理解
- Oracle EBS Export File Format
- 通用数据库行转列的方法
- 对 sql server 数据库的备份进行加密