SQL Server 通過分析執行計劃優化視圖
最近接到一個優化的諮詢,這家公司的系統建立與2004年,目前已經已經運行14之久,這大概是我遇到運行最久的主業務系統了;
項目簡介:
物流公司業務基幹系統(一個基幹系統能用14年,厲害)
數據庫:SQL Server 2000
開發語言:Visual Basic(注意是VB,不是VB.NET)
系統架構:C/S
問題描述:
系統太久遠,當時開發的人員只剩下了一個,且快到了退休年齡,年輕人已經沒幾個人會VB了.現在每一個業務改動都非常困難,且系統運行效率特別低.有時候一個操作需要20秒以上;
解決方案:
長遠的解決方案當然是重新開發系統,按照最新的公司戰略和當下的技術結構重構系統.這這不是一蹴而就的事情,對於解決方案供應商第一任務是取得客戶信任,然後再規劃將來的事情;對於客戶來說,比較重要的事情如何解決目前效率的問題.所以我們做的第一件事情是通過解決客戶效率問題取得客戶信任.VB的代碼牽涉衆多,不適合早期的優化,早期優化從優化數據庫開始;
數據庫優化
我們拿最常用的一個批貨視圖開始優化;這個視圖拿到的第一眼是暈掉的,查詢這麼多表,這麼多子查詢和計算.在我們的測試機上執行時間爲52秒;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
SET QUOTED_IDENTIFIER
ON
GO
SET ANSI_NULLS
ON
GO
ALTER
VIEW _Consoled
AS
SELECT MAWB.MAWB, MAWB.FlightDate,
CASE
WHEN Mawb.Agent
IS
NULL
THEN MAWB.Flight + Mawb.Des3
ELSE Mawb.Flight + Mawb.Des3 +
'('
+ Mawb.Agent +
')'
END
AS Flight, MAWB.Flight
AS MawbFlight,
MAWB.Dest1, MAWB.Dest2, HAWB.WareHouseSN,
Customer.CustName, MAWB.Agent, MAWB.Pieces1, MAWB.Gross1,
MAWB.V1, MAWB.Weight1, MAWB.Pieces2, MAWB.Gross2,
MAWB.V2, MAWB.Weight2,
CASE
WHEN MAWB.Status16 =
1
THEN
'自報關'
ELSE
''
END + MAWB.OP
AS OP,
CASE MAWB.Printer
WHEN
''
THEN
''
ELSE
'總'
END
AS Printed,
HAWB.Pieces2
AS PCS, HAWB.Gross4
AS WGT, HAWB.V4
AS VLM,
CASE HAWB.Num3
WHEN
0
THEN
''
ELSE
'*'
END
AS Broken,
CASE Hawb.HWStatus
WHEN
0
THEN
ltrim(
str( Hawb.Pieces1))
WHEN
2
THEN
ltrim(
str( Hawb.Pieces1)) +
'(' +
ltrim(
str( Hawb.Pieces2))
+
')'
ELSE
ltrim(
str( Hawb.Pieces2))
END
AS Pieces,
CASE Hawb.HWStatus
WHEN
0
THEN Hawb.Gross1
ELSE Hawb.Gross4
END
AS Gross,
CASE Hawb.HWStatus
WHEN
0
THEN HAWB.V1
ELSE Hawb.V4
END
AS V,
CASE Hawb.HWStatus
WHEN
0
THEN HAWB.Weight1
ELSE Hawb.Weight4
END
AS Weight, HAWB.HAWB, HAWB.BGNote,
CASE Hawb.Status11
WHEN
0
THEN
''
ELSE
'*'
END
AS DCZ,
CASE
WHEN BGDS.BGDs
IS
NULL
THEN
''
ELSE
CASE BGDS.Status8
WHEN
0
THEN
'*'
ELSE
'**'
END
END + HAWB.Des4
+
CASE Hawb.Num10
WHEN
0
THEN
''
ELSE
'扣'
END +
CASE Hawb.HWStatus
WHEN
0
THEN
' '
WHEN
1
THEN
'H'
WHEN
2
THEN
'B'
WHEN
3
THEN
'C'
END +
CASE
Hawb.Num3
WHEN
0
THEN
''
ELSE
'破'
END +
CASE HAWB.Status26
WHEN
2
THEN
'T'
ELSE
''
END +
CASE Hawb.DanZhengStatus
WHEN
0
THEN
' '
ELSE
CASE
HAWB.Status19
WHEN
0
THEN
'D'
ELSE
'M'
END
END +
CASE Hawb.Status14
WHEN
0
THEN
' '
WHEN
1
THEN
'到'
WHEN
2
THEN
'籤'
WHEN
3
THEN
'拆'
WHEN
4
THEN
'問'
WHEN
5
THEN
'放'
WHEN
6
THEN
'審'
WHEN
7
THEN
'計'
WHEN
8
THEN
'完'
END
+
CASE Hawb.Status9
WHEN
0
THEN
' '
ELSE
'S'
END +
CASE Hawb.FStatus
WHEN
0
THEN
' '
ELSE
'A'
END +
CASE Hawb.QStatus
WHEN
0
THEN
' '
ELSE
'P'
END +
CASE
Hawb.Status16
WHEN
0
THEN
' '
ELSE
'標'
END +
CASE
WHEN Housing.Category
IS
NULL
THEN
''
ELSE Housing.Category
END +
CASE MAWB.Status13
WHEN
1
THEN
'區內'
WHEN
2
THEN
'區外'
ELSE
''
END +
CASE
WHEN outPlans.WareHouseSN
IS
NULL
THEN
''
ELSE
'排'
END
AS
Status, HAWB.Status14, HAWB.Status17, HAWB.Status27,
Housing.STPosition, Housing.Category, MAWB.Status10, MAWB.Status11,
MAWB.Status12, MAWB.Status13, Destination.City, AirLines.AirLine,
MAWB.AirPort, HAWB.Dest, HAWB.Num1, HAWB.ProcedureStatus,
HAWB.PrintOut, HAWB.PrintRequest, MAWB.Status2,
MAWB.Status7, MAWB.Status8, MAWB.Status4, MAWB.Des1,
CASE
WHEN MAWB.DeclareDate
IS
NULL
OR
MAWB.DeclareDate = MAWB.FlightDate
THEN
''
ELSE
'報關日' +
RIGHT(
CONVERT(
Varchar(
10),
MAWB.DeclareDate,
120),
5)
END
AS DeclareDate,
HAWB.Status10
AS HawbStatus10, BGD.BGStatus,
CASE
WHEN SubmitOFFlight.Submit
IS
NULL
THEN
''
ELSE SubmitOFFlight.Submit
END
AS Submit, BGDS.PCS
AS BGPCS,
BGDS.WGT
AS BGWGT, HAWB.Des2, HAWB.Des5,
''
as Regular
FROM HAWB
RIGHT
OUTER
JOIN
Destination
LEFT
OUTER
JOIN
AirLines
ON
Destination.AirLine = AirLines.AirLineCode
RIGHT
OUTER
JOIN
MAWB
ON Destination.Code = MAWB.Dest2
ON
HAWB.MAWB = MAWB.MAWB
LEFT
OUTER
JOIN
OutPlans
ON
HAWB.WareHouseSN = OutPlans.WareHouseSN
LEFT
OUTER
JOIN
Customer
ON HAWB.CustID = Customer.CustID
LEFT
OUTER
JOIN
(
SELECT Store.WareHouseSN,
MAX( Store.STPosition)
AS STPosition,
CASE
MAX( WareHouse.Category)
WHEN
1
THEN
'新'
WHEN
2
THEN
'老'
WHEN
3
THEN
'虹'
WHEN
4
THEN
'阪'
END
AS
Category
FROM
Store
LEFT
OUTER
JOIN
WareHouse
ON
Store.STPosition = WareHouse.STPosition
GROUP
BY Store.WareHouseSN) Housing
ON
HAWB.WareHouseSN = Housing.WareHouseSN
LEFT
OUTER
JOIN
(
SELECT
DISTINCT WareHouseSN
AS BGStatus
FROM BGD) BGD
ON
HAWB.WareHouseSN = BGD.BGStatus
LEFT
OUTER
JOIN
SubmitOFFlight
ON
LEFT( MAWB.MAWB,
3) = SubmitOFFlight.Code
AND
MAWB.Flight = SubmitOFFlight.Flight
AND
MAWB.AirPort = SubmitOFFlight.AirPort
LEFT
OUTER
JOIN
(
SELECT WareHouseSN,
COUNT(MK)
AS BGDs,
MIN(Status8)
AS Status8,
SUM(
CASE
WHEN Copystatus
IS
NULL
AND
Increase =
0
THEN Pieces
ELSE
0
END)
AS PCS,
SUM(
CASE
WHEN CopyStatus
IS
NULL
THEN Weight
ELSE
0
END)
AS WGT
FROM BGD
GROUP
BY WarehouseSN) BGDS
ON
HAWB.WareHouseSN = BGDS.WareHouseSN
|
視圖舊的執行計劃
通過執行計劃分析可以得知以下問題:
1: MAWB 全表掃描 13% 數據 417
1
2
3
|
--在MAWB主要的查詢時間段FlightDate 添加索引
create
index index_mawb_FlightDate
on dbo.mawb (FlightDate);
|
2: HWAB 主鍵索引全表掃描 39% 數據 10萬
MAWB.MAWB = HAWB.MAWB
在HWAB中,MAWB 字段沒有索引,數據庫執行計劃判斷通過主鍵進行全部掃描,耗費最多的時間,
1
2
3
4
5
|
--關聯條件HAWB.MAWB上加索引
create
index index_hawb_hawb
on dbo.hawb (MAWB) ;
--處理時間由 26秒 到 1.457秒
|
3: BGD 索引全表掃描 11% 數據 7萬 實際數據7萬,全部訪問
BGD 索引全表掃描 11% 數據 7萬 實際數據7萬,全部訪問,兩次訪問
每次3.5秒左右,兩次7秒;
1
2
3
4
5
6
7
|
--添加索引
create
index index_bgd_WareHouseSN
on dbo.bgd (WareHouseSN);
--且通過臨時表改爲訪問一次
--處理時間由 7秒 到 0.355秒
|
4: BGD.WarehouseSN = HAWB.WarehouseSN 7% 實際數據 417,Store 索引全表掃描 7% 實際數據 10萬
1
2
3
4
5
|
--添加複合索引
create
index index_store_WareHouseSN_STPosition
on dbo.Store (WareHouseSN,STPosition);
--處理時間 5.83 -> 0.295
|
5: 計算列
1
2
3
|
在查詢列中有大量計算,影響性能
由於時間關係,以及更改計算列後會影響實際業務邏輯,需要測試,我們在這裏沒有優化;
優化的思路是:儘量不要在數據庫中做列�le:none;">
1
2
3
|
在查詢列中有大量計算,影響性能
由於時間關係,以及更改計算列後會影響實際業務邏輯,需要測試,我們在這裏沒有優化;
優化的思路是:儘量不要在數據庫中做列計算,可以將數據查詢出來在代碼中進行計算;如果非要在數據庫計算,儘量計算少的列,且在內存中計算;
|
6: 其他
1
2
3
4
|
不太影響效率的全表掃描
--添加複合索引SubmitOFFlight
create
index index_SubmitOFFlight_Code
on dbo.SubmitOFFlight (Code,Flight,AirPort) ;
|
添加索引後的執行計劃
通過兩個執行計劃的對比,我們發現之前的全表掃描已經替換成了索引掃描;整體銷量也提高了10倍左右;此時較大的花費主要集中在Bookmark Lookup 標籤查找上面,由於查詢列過多,我們也無法做覆蓋索引.如果想提高Bookmark Lookup的查詢效率,猜測通過更換SSD硬盤可以提升很多;
6秒鐘感覺還是很慢,可能sql server 2000的引擎優化不好,相似配置環境下,遷移到sql server 2008的服務器上面(兩機器內存均爲4G,2008機器E5 cpu 2.4Ghz*2 臺式機,2000機器I3 cup 3.07Ghz 服務器,理論上2008的機器配置更低),同樣情況之下2秒;如果升級到sql server 2014 ,開啓內存模式,應該就可以秒查了;
以上的思路是通過增加視圖和升級服務器和硬件的思路.添加視圖的方式最簡單和成本低,基本上不需要更改任何代碼;缺點是可能效率還達不到要求;升級服務器和硬件的方式也簡單見效,缺點是費用較高;兩者可以結合使用.現在還有第三個思路,就是把視圖更改爲存儲過程,用臨時表過濾更多的數據和減少數據庫的訪問次數.實際情況提升速度並不明顯,由視圖到存儲過程,執行時間大概由6秒下降到3秒,提高100%左右;
以上的優化都是基於技術層面,沒有牽涉到業務邏輯的優化,實際上通過業務邏輯的優化,也可以大幅增加腳本的查詢速度,時間短搞不清業務邏輯的情況下不建議優化邏輯,風險比較大;
存儲過程的思路:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|