SQL SERVER 2014 下IF EXITS 居然引起執行計划變更的案例分享


  這個問題是在SQL SERVER 2005 升級到SQL SERVER 2014的測試過程中一同事發現的。我覺得有點意思,遂稍微修改一下腳本展示出來,本來想構造這樣的一個案例來演示,但是畏懼麻煩,遂直接貼上原表,希望Leader不要叼我(當然個人覺得真沒啥,兩張表名而已,真泄露不了啥信息)。

    腳本如下所示,非常簡單的一段SQL語句,我將其分為SQL1、SQL2、SQL3.  其實SQL2、SQL3是差不多的,唯一的區別在於多了一個IF EXISTS

DECLARE @Operation_Code CHAR(3) ,
    @FNCardList VARCHAR(1000) ,
    @RollList VARCHAR(1000) ,
    @White VARCHAR(20) ,
    @OneMinute VARCHAR(20) ,
    @Operator VARCHAR(20) ,
    @Is_NoWait BIT ,
    @HoldCards VARCHAR(3000);            
 
 
SELECT  @Operation_Code = '999' ,
        @FNCardList = 'A15309913' ,
        @RollList = 'A15309913';
 
 
--SQL 1
DECLARE @FNCardTable TABLE ( Iden INT, FN_Card CHAR(9) ); 
 
 
INSERT  INTO @FNCardTable
        SELECT  Iden ,
                [No]
        FROM    PUBDB.dbo.udf_ConvertStrToTable(@FNCardList, ',') a;            
 
 
--SQL 2          
SELECT  1
FROM    dbo.fnRepairOperation a WITH ( NOLOCK )
        INNER JOIN @FNCardTable b ON CHARINDEX(b.FN_Card, a.FN_Card) > 0
        INNER JOIN dbo.fnJobTraceHdr c WITH ( NOLOCK ) ON c.FN_Card = b.FN_Card
                                                          AND c.Current_Department = a.Current_Department
WHERE   a.Check_Time IS NULL
        AND a.Is_Ignore = 0;
 
PRINT ( @Operation_Code );     
 
 
--SQL 3      
 
IF EXISTS ( SELECT   1
            FROM    dbo.fnRepairOperation a WITH ( NOLOCK )
                    INNER JOIN @FNCardTable b ON CHARINDEX(b.FN_Card,
                                                        a.FN_Card) > 0
                    INNER JOIN dbo.fnJobTraceHdr c WITH ( NOLOCK ) ON c.FN_Card = b.FN_Card
                                                        AND c.Current_Department = a.Current_Department
            WHERE   a.Check_Time IS NULL
                    AND a.Is_Ignore = 0 )
    BEGIN            
        RAISERROR('返回錯誤!', 16, 1);            
        RETURN;            
    END

在SQL SERVER 2005的環境中,整個批處理的SQL執行只需要不到1秒的樣子。我們也能看到執行計划的COST對比值為0%,99%,1%。

clipboard

在SQL SERVER 2014(SQL Server 2014 - 12.0.2000.8 Standard Edition )中執行時間突然變成了4分41秒。 最奇怪的是查詢計划的COST比值依然為1%,99%,0%。實際測試發現這個COST的比值是不准確的。因為單獨執行SQL1、SQL2只需要一秒。但是執行SQL3就需要4分多鍾。(當然SQL SERVER 2005 與SQL SERVER 2014的數據,索引是一致的,細心的人會注意下面提示缺少索引,加上這個索引依然慢的出奇,這個影響因素完全可以忽略)

clipboard[1]

 

SQL 2的實際執行計划如下所示

clipboard[2]

 

SQL 3的實際執行計划如下所示

clipboard[3]

另外,表dbo.fnRepairOperation的記錄數有332553,dbo.fnJobTraceHdr 的記錄數為110058。表變量@FNCardTable記錄數為1.對比執行計划,我們可以看到兩者的Nested Loops的外部表變化了,從表變量@FNCardTable變成了dbo.fnRepairOperation

我們先來看看SQL2執行計划里面的一些詳細信息,我們可以看到外邊循環表為@FNCardTable,循環次數為1(Actual Number of Rows 值為1),內部循環表為dbo.fnJobTraceHdr,循環次數為1(Number of Executions為1),符合條件的記錄集數據為1條(Actual Number of Rows 值為1)

clipboard[4]

clipboard[5]

那么再來看SQL3, 外部循環表變為dbo.fnRepairOperation,它走表掃描(Table Scan),循環次數為432(Actual Number of Rows),內部循環表為dbo.fnJobTraceHdr, 走索引掃描,總共循環了47545056次,這個值怎么來的呢? 因為內部循環表中符合記錄數為110058(表dbo.fnJobTraceHdr的記錄數), 110058*432 = 47545056,也就是說總共循環了四千七百多萬次。 偶的神啊。難怪如此之慢。起初,我以為是統計信息不准確導致數據庫優化器選擇了錯誤的執行計划,於是我更新了這兩個表的統計信息,甚至連索引也重建了。結果還是如此。看來的確是優化器沒有選擇最優的執行計划。但是沒有IF EXITS它又是正常的, 加了IF EXITS后執行計划就變成這個鳥樣。說不清是優化器的bug還是算法問題所導致。

clipboard[6]

clipboard[7]

 

那么怎么解決這個問題,可以用聯接提示(HASH JOIN HINT)指定SQL語句走HASH JOIN,此時批處理的SQL語句可以1秒出來。另外就是改寫該SQL語句的寫法。在此不做過多闡述

IF EXISTS ( SELECT   1
            FROM    dbo.fnRepairOperation a WITH ( NOLOCK )
                    INNER JOIN @FNCardTable b ON CHARINDEX(b.FN_Card,
                                                        a.FN_Card) > 0
                    INNER HASH JOIN  dbo.fnJobTraceHdr c WITH ( NOLOCK ) ON c.FN_Card = b.FN_Card
                                                        AND c.Current_Department = a.Current_Department
            WHERE   a.Check_Time IS NULL
                    AND a.Is_Ignore = 0 )
    BEGIN            
        RAISERROR('部分卡中有 班長新增加的工序或 回修工序,請聯系一下工藝員和當班班長!', 16, 1);            
        RETURN;            
    END; 

其實這個案例也間接驗證了嵌套循環連接,隨着數據量的增長,這種方式對性能的消耗將呈現出指數級別的增長。


免責聲明!

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



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