​ SQL Server 通過分析執行計劃優化視圖

​ 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
相關文章
相關標籤/搜索