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萬多條。

clipboard

執行上面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的條件,怎么會有如此大的性能差距呢? 查看執行計划后,發現加上這樣一個條件后,執行計划完全不同了。

clipboard[1]

我姑且將執行性能較好的SQL的執行計划叫做Plan A,執行性能很差的SQL的執行計划叫做Plan B

Plan A

clipboard[2]

Plan B

clipboard[3]

如上所示,Plan B 看似開銷都耗費在鍵查找那一塊,但是如果查看具體信息(如下所示),並無特別地方。

clipboard[4]

於是我使用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')

clipboard[5]

於是我將懷疑的地方轉移到表連接方式,使用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也完全不准確。讓人有點匪夷所思。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM