最近呢,總是有人反饋公司的一個系統頁面反應太慢,一個列表展示,竟然需要二三十秒,我上去點了點,發現沒二三十秒那么誇張,但是也是夠慢了,龜速,直接影響工作效率啊。所以從這星期開始,我便硬着頭皮上優化系統了。
這個系統使用MVC3+Easy UI做的,數據庫用的是SQL Server 2012。優化呢,首先就是定位原因,剛開始呢,只能按傳統方法來,想想瓶頸在哪,網絡傳輸,程序響應和頁面渲染,磁盤IO。前兩個我是沒法控制了,只有想想最后的法子了,從數據庫入手了。
上圖就是執行頁面很緩慢的界面。首先監控到其sql語句,因為手里有源代碼,直接就用程序調試獲取了,不用 SQL Server Profiler。拿到sql語句后,就開始對sql進行分析,看看哪里能進行優化。一條真理:盡可能的避免全表掃描。
SELECT TOP 10 * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY CREATEDATE DESC) AS RowNumber,b.* FROM ( SELECT WO.WorkOrderCode,WO.WorkOrderType,WO.Status,WO.CustName,WO.CustTel1,WO.CustTel2,WO.CustIdentityNo,WO.CardId,WO.CustSex,WO.CustAge,WO.CustBirthday, WO.IsMember,WO.AccountLevel,WO.MemberDate,WO.AccountScore,WO.DestTarget,WO.DestTargetDesc,WO.Source,WO.ToType,WO.Dept,WO.HotelId,WO.HotelName, WO.HotelCreateDate,WO.HotelType,WO.HotelBrand,WO.HotelTel,WO.HotelBigArea,WO.HotelArea,WO.HotelCityGroup,'' AS HotelCity,WO.Title,WO.SelectTitle,WO.Content,WO.IsAccept,WO.CompensateDept, WO.CompensateAmountType,WO.CompensateAmount,WO.ReferDept,WO.ResponsiblePerson,W2.Attribute3 AS ResponsiblePersonName,WO.ResponsibleDate,WO.ClosePerson, WO.CloseDate,WO.CatePerson,WO.CateDate,WO.ScorePerson,WO.ScoreDate,WO.CreateUser,WO.CreateDate,WO.UpdateUser,WO.UpdateDate,W1.Attribute3 AS UserName FROM WorkOrder WO WITH (NOLOCK) LEFT JOIN WSCUser W1 ON WO.CreateUser=W1.UserID LEFT JOIN WSCUser W2 ON WO.ResponsiblePerson=W2.UserID WHERE 1=1 AND (WO.Title LIKE '%其他%' OR WO.SelectTitle LIKE '%其他%') AND WO.CreateDate BETWEEN '2017-09-03 00:00:00' AND '2017-09-30 23:59:59' AND WO.STATUS=3 AND W2.Attribute3 ='張三' AND WO.WorkOrderType='T' AND WO.IsDelete=0 ) b ) a WHERE RowNumber > 0 order by rownumber
1. 從里往外看吧,最里層的子查詢,是3個表的左連接查詢,這個地方可以采用的方案有兩種:
①在連接字段WO表的字段CreateUser上建立索引,②把這個聯合查詢做成視圖(此法效果不好)。
2.之后再看where后邊,
①Title中用了like查詢,可以改成=,但是標題那個地方要求有模糊搜索功能(此法不可取)。
②對於status(三種狀態),workordertype(四種狀態),IsDelete(兩種狀態)這些建立索引,但是索引建立之后,效果很差,因為對於這種區別度很小的字 段,建立索引后,查詢時還會增加 KeyLookup(標簽查找 ),性能並不一定會提升(此法效果不好)。
③對於這種條件不固定的查詢,基本上可以把多列索引這種方案給排除了,查詢出來二三十個字段,也可以把覆蓋索引給排除了(此法不可取)。
④最后只能選擇在CreateDate字段上建立索引了(此法可取)。
3.對於分頁算法,分頁算法有好幾種,經過考慮,這種用ROW_NUMBER()的方法效率還可以。所以說換一個分頁算法,需要改大量程序代碼,這種方法也是不可取的。
綜上所述,選擇的方法只有是在CreateDate上建立索引了。
4.之后還有一部操作,如下圖所失,將索引重新組織一下,因為時間長了,碎片比較多。
注意:進行重新組織索引時,一定不要在程序訪問量大時進行,因為會造成鎖表,系統直接無法使用。這個過程是很有風險的,數據量太大,執行也是需要一定時間的。
本來也為好了,可是故事未完待續,我把這段sql在數據庫中執行,發現用時只有0.3s左右,但是系統中能看到結果,還需要1.4s左右。我剛開始還以為是系統代碼太爛,EasyUI框架太重的原因。又調試了一會兒才發現是,程序執行了分頁語句之后,又執行了一段算數據總數的sql,原來是count()惹的貨。
select count(*) from ( SELECT WO.WorkOrderCode,WO.WorkOrderType,WO.Status,WO.CustName,WO.CustTel1,WO.CustTel2,WO.CustIdentityNo,WO.CardId,WO.CustSex,WO.CustAge,WO.CustBirthday, WO.IsMember,WO.AccountLevel,WO.MemberDate,WO.AccountScore,WO.DestTarget,WO.DestTargetDesc,WO.Source,WO.ToType,WO.Dept,WO.HotelId,WO.HotelName, WO.HotelCreateDate,WO.HotelType,WO.HotelBrand,WO.HotelTel,WO.HotelBigArea,WO.HotelArea,WO.HotelCityGroup,'' AS HotelCity,WO.Title,WO.SelectTitle,WO.Content,WO.IsAccept,WO.CompensateDept, WO.CompensateAmountType,WO.CompensateAmount,WO.ReferDept,WO.ResponsiblePerson,W2.Attribute3 AS ResponsiblePersonName,WO.ResponsibleDate,WO.ClosePerson, WO.CloseDate,WO.CatePerson,WO.CateDate,WO.ScorePerson,WO.ScoreDate,WO.CreateUser,WO.CreateDate,WO.UpdateUser,WO.UpdateDate,W1.Attribute3 AS UserName FROM WorkOrder WO WITH (NOLOCK) LEFT JOIN WSCUser W1 ON WO.CreateUser=W1.UserID LEFT JOIN WSCUser W2 ON WO.ResponsiblePerson=W2.UserID WHERE 1=1 AND (WO.Title LIKE '%其他%' OR WO.SelectTitle LIKE '%其他%') AND WO.CreateDate BETWEEN '2016-05-03 00:00:00' AND '2017-09-30 23:59:59' AND WO.STATUS=3 AND WO.WorkOrderType='T' AND WO.IsDelete=0 ) a
上圖就是算總數據量的語句,這個語句的優化可以這樣。
1.count(*)可以改成count(WorkOrderCode),WorkOrderCode是WorkOrder的主鍵,經過測試執行效率要快於count(*)。--詳情請見
2.將查詢字段全部去掉。
優化完成之后,語句變成這樣。
SELECT count(WorkOrderCode) FROM WorkOrder WO WITH (NOLOCK) LEFT JOIN WSCUser W1 ON WO.CreateUser=W1.UserID LEFT JOIN WSCUser W2 ON WO.ResponsiblePerson=W2.UserID WHERE 1=1 AND (WO.Title LIKE '%其他%' OR WO.SelectTitle LIKE '%其他%') AND WO.CreateDate BETWEEN '2016-05-03 00:00:00' AND '2017-09-30 23:59:59' AND WO.STATUS=3 AND WO.WorkOrderType='T' AND WO.IsDelete=0
到這里,優化算是正式結束了,搞了三四天。在這過程中也對數據庫的索引,語句優化有了更加深刻的認識。當然有些也顛覆了我以前看到理論,如聯合查詢建成視圖后,查視圖效率會提升,但是我做的實驗並非如此,視圖效率不如聯合索引(詳情實驗--視圖查詢性能的新認識)。
拋開技術上的因素,還有很多業務上的需要考慮,像剛開始的那個查詢條件表單,有些選項可能就很少使用,但剛開始我也考慮了很多種情況,后來,跟客服人員溝通,發現他們有些選項就不怎么用,之后我按照他們給的經常使用的篩選條件,進行優化,也就不那么盲目了,問題也好處理了。通過這件事呢,總結出兩條哲理:“干活兒不由東,累死也誤工”、“溝通很重要”。
世事洞明皆學問 人情練達即文章,技術始終是要為人而服務的,所以不要為了技術而技術。