化繁為簡——分解復雜的SQL語句


今天同事咨詢一個SQL語句,如下所示,SQL語句本身並不復雜,但是執行效率非常糟糕,糟糕到一塌糊塗(執行計划也是相當復雜)。如果查詢條件中沒有NOT EXISTS部分,倒是不要一秒就能查詢出來。

SELECT * FROM dbo.UVW_PDATest a WITH(NOLOCK)
WHERE  
Remark='前紡' AND Operation_Name='粗紗' AND One_Status_Code='0047'  
         AND a.Createtime >='2015-9-23'
AND  NOT EXISTS
        (
          SELECT 1 FROM dbo.UVW_PDATest c WITH(NOLOCK)
          WHERE a.Task_NO =c.Task_NO AND c.One_Status_Code='0014'
        )

為什么如此簡單的SQL語句,執行效率卻一塌糊塗呢,因為UVW_PDATest是一個視圖,而且該視圖是由8個表關聯組成。

SELECT ..........
From dbo.PDA_TB_Produce a With(Nolock)
  Join dbo.DctOperationList b With(Nolock)
   On a.Operation_Code=b.Operation_Code
  Join dbo.DctOneStatusList c With(Nolock)
   On a.One_Status_Code=c.One_Status_Code
  Left join dbo.DctTwoStatusList d With(Nolock)
   On c.One_Status_Code=d.One_Status_Code and a.Two_Status_Code=d.Two_Status_Code
  left Join dbo.DctMachineList e With(Nolock)
   On a.Operation_Code=e.Operation_Code and a.Machine_Code=e.Machine_Code
  left Join dbo.DctOperationList f With(Nolock)
   On a.Next_Operation_Code=f.Operation_Code
  Join dbo.DctUserList g With(Nolock)
   On a.User_ID_Operating=g.User_ID
  Join dbo.DctUserList h With(Nolock)
   On a.User_ID=h.User_ID

剛開始我想從索引上去優化,加上一兩個索引后發現其實並無多大益處。為什么性能會如此糟糕呢?原因是什么呢?

大量復雜的Join

該類查詢模式包含了大量連接,尤其是連接條件是不等連接,由於統計信息隨着表連接的增多精度逐漸下降,這會導致低效的查詢性能。解決這類情況可以通過分解查詢,並將中間解決存入臨時表解決。 具體參考博客:什么情況下應該分解復雜的查詢來提升性能

於是我拆分上面SQL語句(如下所示),先將執行結果保存到臨時表,然后關聯取數,結果一秒鍾的樣子就執行出來了。真可謂是化繁為簡。

SELECT Task_NO INTO #TMP_MID_UVW_PDATest
FROM dbo.UVW_PDATest c WITH(NOLOCK)
  WHERE One_Status_Code='0014' and Remark='前紡' AND Operation_Name='粗紗'
         
SELECT * INTO #TMP_UVW_PDATest
FROM dbo.UVW_PDATest a WITH(NOLOCK)
WHERE   Remark='前紡'
 AND Operation_Name='粗紗'
 AND One_Status_Code='0047'  
    AND Create_Date>='2015-9-23' ;
         
SELECT  * FROM #TMP_UVW_PDATest a
    WHERE NOT EXISTS(SELECT 1 FROM #TMP_MID_UVW_PDATest c WHERE a.Task_NO =c.Task_NO );
 
DROPTABLE#TMP_UVW_PDATest
DROP TABLE #TMP_MID_UVW_PDATest

 

第二個案例是ORACLE數據庫的一個優化案例,具體SQL語句如下所示,執行時間非常長,一般都是二十多秒左右。

SELECT A.SC_NO,
   A.MRP_GROUP_CD, 
   A.DIMM_ID, 
   A.JOB_ORDER_NO, 
   DECODE(SIGN(A.DEMAND_QTY),-1,0,A.DEMAND_QTY) AS DIFF_QTY, 
   A.ASSIGNED_TYPE 
FROM 
   (
   SELECT CC.SC_NO,
      BB.MRP_GROUP_CD, 
      BB.DIMM_ID, 
      BB.JOB_ORDER_NO, 
      NVL (SUM (BB.DEMAND_QTY), 0) - NVL(SUM(REC.RECV_QTY),0) AS DEMAND_QTY, 
      CASE 
         WHEN DD.REQ_DATE<TRUNC(SYSDATE) THEN 'AH' 
         ELSE 'AS' 
      END AS ASSIGNED_TYPE 
   FROM MRP_JO_DEMAND BB,
      PO_HD CC ,
      (
      SELECT JOB_ORDER_NO,
         DIMM_ID,
         SUM(RECV_QTY) AS RECV_QTY 
      FROM MRP_AGPO_SCHD_RECV_SPECIFIC 
      GROUP BY JOB_ORDER_NO,
         DIMM_ID 
      ) 
      REC,
      MRP_JO_ASSIGN DD 
   WHERE BB.JOB_ORDER_NO=CC.PO_NO 
  AND BB.JOB_ORDER_NO=REC.JOB_ORDER_NO(+) 
  AND BB.DIMM_ID=REC.DIMM_ID(+) 
  AND BB.JOB_ORDER_NO = DD.JOB_ORDER_NO(+) 
  AND BB.DIMM_ID = DD.DIMM_ID(+) 
  AND BB.MRP_GROUP_CD=DD.MRP_GROUP_CD(+) 
  AND EXISTS 
      (
      SELECT 1 
      FROM MRP_DIMM AA 
      WHERE AA.MRP_GROUP_CD=BB.MRP_GROUP_CD 
     AND AA.DIMM_ID=BB.DIMM_ID 
     AND AA.JOB_ORDER_NO=BB.JOB_ORDER_NO 
      ) 
   GROUP BY CC.SC_NO,
      BB.MRP_GROUP_CD, 
      BB.DIMM_ID, 
      BB.JOB_ORDER_NO,
      DD.REQ_DATE
   ) 
   A, 
   INVSUBMAT.INV_MRP_JO_AVAILABLE_V B 
WHERE A.JOB_ORDER_NO = B.JOB_ORDER_NO 
AND A.MRP_GROUP_CD = B.MRP_GROUP_CD 
AND A.DIMM_ID = B.DIMM_ID 
AND NVL (A.DEMAND_QTY, 0) < NVL (B.AVAILABLE_QTY, 0) 
AND NVL (B.AVAILABLE_QTY, 0)>0 
ORDER BY A.MRP_GROUP_CD,
   A.DIMM_ID,
   A.JOB_ORDER_NO;

clipboard

查看執行計划,你會發現COST主要耗費在HASH JOIN上。如下截圖所示,表INV_STOCK_ASSIGN來自於視圖INVSUBMAT.INV_MRP_JO_AVAILABLE_V。

clipboard[1]

將上面復雜SQL拆分后,執行只需要不到一秒解決,如下截圖所示,速率提高了幾十倍。優化往往有時候很復雜,有時候也很簡單,就是將復雜的語句拆分成簡單的SQL語句,性能的提升有時候確實令人吃驚!

CREATE GLOBAL TEMPORARY TABLE TMP_MRP_MID_DATA
( SC_NO           VARCHAR2(20) ,
 MRP_GROUP_CD    VARCHAR2(10) ,
  DIMM_ID          NUMBER,
  JOB_ORDER_NO    VARCHAR2(20) ,
  DEMAND_QTY      NUMBER       ,
  DIFF_QTY        NUMBER       ,
  ASSIGNED_TYPE   VARCHAR(2)
) ON COMMIT PRESERVE ROWS;
 
 
INSERT INTO TMP_MRP_MID_DATA
SELECT A.SC_NO,
   A.MRP_GROUP_CD, 
   A.DIMM_ID, 
   A.JOB_ORDER_NO, 
   A.DEMAND_QTY,
   DECODE(SIGN(A.DEMAND_QTY),-1,0,A.DEMAND_QTY) AS DIFF_QTY, 
   A.ASSIGNED_TYPE 
FROM 
   (
   SELECT CC.SC_NO,
      BB.MRP_GROUP_CD, 
      BB.DIMM_ID, 
      BB.JOB_ORDER_NO, 
      NVL (SUM (BB.DEMAND_QTY), 0) - NVL(SUM(REC.RECV_QTY),0) AS DEMAND_QTY, 
      CASE 
         WHEN DD.REQ_DATE<TRUNC(SYSDATE) THEN 'AH' 
         ELSE 'AS' 
      END AS ASSIGNED_TYPE 
   FROM MRP_JO_DEMAND BB
      INNER JOIN  PO_HD CC ON BB.JOB_ORDER_NO=CC.PO_NO
        LEFT JOIN  (
      SELECT JOB_ORDER_NO,
         DIMM_ID,
         SUM(RECV_QTY) AS RECV_QTY 
      FROM MRP_AGPO_SCHD_RECV_SPECIFIC 
      GROUP BY JOB_ORDER_NO,
         DIMM_ID 
      ) 
   REC ON  BB.JOB_ORDER_NO=REC.JOB_ORDER_NO AND BB.DIMM_ID=REC.DIMM_ID
      LEFT JOIN  MRP_JO_ASSIGN DD ON BB.JOB_ORDER_NO = DD.JOB_ORDER_NO   AND BB.DIMM_ID = DD.DIMM_ID  AND BB.MRP_GROUP_CD=DD.MRP_GROUP_CD
      INNER JOIN MRP_DIMM AA  ON AA.MRP_GROUP_CD=BB.MRP_GROUP_CD   AND AA.DIMM_ID=BB.DIMM_ID    AND AA.JOB_ORDER_NO=BB.JOB_ORDER_NO  
   GROUP BY CC.SC_NO,
      BB.MRP_GROUP_CD, 
      BB.DIMM_ID, 
      BB.JOB_ORDER_NO,
      DD.REQ_DATE
   ) 
   A;
   COMMIT;
 
SELECT A.* FROM
TMP_MRP_MID_DATA A INNER JOIN
   INVSUBMAT.INV_MRP_JO_AVAILABLE_V B  ON A.JOB_ORDER_NO = B.JOB_ORDER_NO 
          AND A.MRP_GROUP_CD = B.MRP_GROUP_CD 
            AND A.DIMM_ID = B.DIMM_ID 
WHERE
NVL (A.DEMAND_QTY, 0) < NVL (B.AVAILABLE_QTY, 0) 
AND NVL (B.AVAILABLE_QTY, 0)>0 
ORDER BY A.MRP_GROUP_CD,
   A.DIMM_ID,
   A.JOB_ORDER_NO;

clipboard[2]

 

小結:

1:越是復雜的SQL語句,優化器越是容易選擇一個糟糕的執行計划(優化器之所以難以選定最優的執行計划,是因為優化器要平衡選定最優執行路徑的代價,不能一味為了選擇最優執行計划,而將復雜SQL的所有執行路徑都計算對比一遍,往往只能有選擇性的選取一些執行路徑計算對比,否則開銷太大。而越是復雜的SQL,可選擇的執行路徑就是越多。

說得有點繞口,還是打個比方,比如你從廣州到北京,如果就只有飛機(直飛),火車(直達)、汽車(直達)三種選擇,那么想必你能很快給出一個最優的路線(例如,最快的是飛機、最省錢的是火車),但是如果飛機、火車、汽車都不能直達:假如火車票沒有了直達,你必須中途轉幾次、飛機票也沒有直達了,你需要轉機,那么此時選擇性復雜的情況,你就必須花費不少時間才能制定一個最優的計划了。 如果在復雜一點的情況,你從中國去美國,是不是有N種路徑? 如果全部計算對比一遍各種可能的路徑,估計你小腦袋不夠用………………

 

2:執行計划是可以被重用的,越簡單的SQL語句被重用的可能性越高。而復雜的SQL語句只要有一個字符發生變化就必須重新解析,然后再把這一大堆垃圾塞在內存里。可想而知,數據庫的效率會何等低下。

 

3:如果SQL語句過分復雜,要么是業務有問題,要么是模型設計不當。可以說復雜的SQL反映出系統設計方面有不少問題和缺陷。


免責聲明!

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



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