今天同事諮詢一個SQL語句,以下所示,SQL語句自己並不複雜,可是執行效率很是糟糕,糟糕到一塌糊塗(執行計劃也是至關複雜)。若是查詢條件中沒有NOT EXISTS部分,卻是不要一秒就能查詢出來。html
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
剛開始我想從索引上去優化,加上一兩個索引後發現其實並沒有多大益處。爲何性能會如此糟糕呢?緣由是什麼呢? app
因而我拆分上面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;
查看執行計劃,你會發現COST主要耗費在HASH JOIN上。以下截圖所示,表INV_STOCK_ASSIGN來自於視圖INVSUBMAT.INV_MRP_JO_AVAILABLE_V。 spa
將上面複雜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;
小結: code
1:越是複雜的SQL語句,優化器越是容易選擇一個糟糕的執行計劃(優化器之因此難以選定最優的執行計劃,是由於優化器要平衡選定最優執行路徑的代價,不能一味爲了選擇最優執行計劃,而將複雜SQL的全部執行路徑都計算對比一遍,每每只能有選擇性的選取一些執行路徑計算對比,不然開銷太大。而越是複雜的SQL,可選擇的執行路徑就是越多。 htm
說得有點繞口,仍是打個比方,好比你從廣州到北京,若是就只有飛機(直飛),火車(直達)、汽車(直達)三種選擇,那麼想必你能很快給出一個最優的路線(例如,最快的是飛機、最省錢的是火車),可是若是飛機、火車、汽車都不能直達:假如火車票沒有了直達,你必須中途轉幾回、飛機票也沒有直達了,你須要起色,那麼此時選擇性複雜的狀況,你就必須花費很多時間才能制定一個最優的計劃了。 若是在複雜一點的狀況,你從中國去美國,是否是有N種路徑? 若是所有計算對比一遍各類可能的路徑,估計你小腦殼不夠用……………… blog
2:執行計劃是能夠被重用的,越簡單的SQL語句被重用的可能性越高。而複雜的SQL語句只要有一個字符發生變化就必須從新解析,而後再把這一大堆垃圾塞在內存裏。可想而知,數據庫的效率會何等低下。
3:若是SQL語句過度複雜,要麼是業務有問題,要麼是模型設計不當。能夠說複雜的SQL反映出系統設計方面有很多問題和缺陷。