記一次苦逼的SQL查詢優化

最近在維護公司項目時,須要加載某頁面,總共加載也就4000多條數據,居然須要35秒鐘,要是數據增加到40000條,我估計好幾分鐘都搞不定。臥槽,要我是用戶的話估計受不了,趁閒着沒事,就想把它優化一下,走你。 

先把查詢貼上: 
javascript

Sql代碼  
  1. select Pub_AidBasicInformation.AidBasicInfoId,  
  2.    
  3.        Pub_AidBasicInformation.UserName,  
  4.    
  5.        Pub_AidBasicInformation.District,  
  6.    
  7.        Pub_AidBasicInformation.Street,  
  8.    
  9.        Pub_AidBasicInformation.Community,  
  10.    
  11.        Pub_AidBasicInformation.DisCard,  
  12.    
  13.        Pub_Application.CreateOn AS AppCreateOn,  
  14.    
  15.        Pub_User.UserName as DepartmentUserName,   
  16.    
  17.        Pub_Consult1.ConsultId,  
  18.    
  19.        Pub_Consult1.CaseId,  
  20.    
  21.        Clinicaltb.Clinical,AidNametb.AidName,  
  22.    
  23.        Pub_Application.IsUseTraining,  
  24.    
  25.        Pub_Application.ApplicationId,  
  26.    
  27.        tab.num  
  28.    
  29. FROM   Pub_Consult1  
  30.    
  31. INNER JOIN Pub_Application ON Pub_Consult1.ApplicationId = Pub_Application.ApplicationId  
  32.    
  33. INNER JOIN Pub_AidBasicInformation ON Pub_Application.AidBasicInfoId = Pub_AidBasicInformation.AidBasicInfoId                                                             
  34.    
  35. INNER JOIN(select ConsultId,dbo.f_GetClinical(ConsultId) as Clinical  
  36.    
  37.             from Pub_Consult1) Clinicaltb on Clinicaltb.ConsultId=Pub_Consult1.ConsultId  
  38.    
  39. left join (select distinct ApplicationId, sum(TraniningNumber) as num from dbo.Review_Aid_UseTraining_Record  where  AidReferralId is null  group by  ApplicationId) tab on tab.ApplicationId=Pub_Consult1.ApplicationId  
  40.    
  41. INNER JOIN(select ConsultId,dbo.f_GetAidNamebyConsult1(ConsultId) as AidName  from Pub_Consult1) AidNametb on AidNametb.ConsultId=Pub_Consult1.ConsultId                                
  42.    
  43. LEFT OUTER JOIN Pub_User ON Pub_Application.ReviewUserId = Pub_User.UserId  
  44.    
  45.      WHERE Pub_Consult1.Directory = 0  
  46.    
  47.      order by Pub_Application.CreateOn desc  


執行後有圖有真相: 
java

 


這麼慢,沒辦法就去看看查詢計劃是怎麼樣: 
web

 


這是該sql查詢裏面執行三個函數時生成查詢計劃的截圖,一看就知道,執行時開銷比較大,並且都是花費在彙集索引掃描上,把鼠標放到彙集索引掃描的方塊上面,依次看到以下詳細計劃: 
sql

 


 


從這幾張圖裏,能夠看到查詢I/O開銷,運算符開銷,估計行數,以及操做的對象和查詢條件,這些都爲優化查詢提供了有利證據。第1,3張圖IO開銷比較大,第2張圖估計行數比較大,再根據其它信息,首先想到的應該是去創建索引,不行的話再去改查詢。 

先看看數據庫引擎優化顧問能給咱們提供什麼優化信息,有時候它可以幫咱們提供有效的信息,好比建立統計,索引,分區什麼的。 

先打開SQL Server Profiler 把剛剛執行的查詢另存爲跟蹤(.trc)文件,再打開數據庫引擎優化顧問,作以下圖操做 
數據庫

 


最後生成的建議報告以下: 
服務器

 


在這裏能夠單擊查看一些建議,分區,建立索引,根據提示建立了以下索引: 
app

Sql代碼  
  1. CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_AidBasicInformation]  
  2.    
  3. (  
  4.    
  5.     [AidBasicInfoId] ASC  
  6.    
  7. )  
  8.    
  9.    
  10. CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_Application]  
  11.    
  12. (  
  13.    
  14.     [ApplicationId] ASC,[ReviewUserId] ASC,[AidBasicInfoId] ASC,[CreateOn] ASC  
  15.    
  16. )  
  17.    
  18. CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_Consult1]  
  19.    
  20. (  
  21.    
  22.     [Directory] ASC,[ApplicationId] ASC  
  23.    
  24. )  
  25.    
  26.     
  27.    
  28. CREATE NONCLUSTERED INDEX idnex1 ON [dbo].[Review_Aid_UseTraining_Record]  
  29.    
  30. (  
  31.    
  32.     [AidReferralId] ASC,[ApplicationId] ASC  
  33.    
  34. )  


索引建立後,再次執行查詢,原覺得可提升效率,沒想到我勒個去,仍是要30幾秒,幾乎沒什麼改善,優化引擎顧問有時候也會失靈,在這裏只是給你們演示有這種解決方案去解決問題,有時候仍是靠譜的,只是此次不靠譜。沒辦法,只有打開函數仔細瞅瞅,再結合上面的查詢計劃詳細圖,刪除先前建立的索引,而後建立了以下索引: 
函數

Sql代碼  
  1. CREATE NONCLUSTERED INDEX index1 ON dbo.Report_AdapterAssessment_Aid  
  2.    
  3. (  
  4.    
  5.     AdapterAssessmentId ASC, ProductDirAId  ASC  
  6.    
  7. )  
  8.    
  9. CREATE NONCLUSTERED INDEX index1 ON dbo.Report_AdapterAssessment  
  10.    
  11. (  
  12.    
  13.     ConsultId ASC  
  14.    
  15. )  


再次執行查詢 
大數據

 


好了,只需3.5秒,差很少提升10倍速度,看來此次是湊效了哈。 

再來看看查詢計劃是否有改變,上張圖來講明下問題: 
優化

 


從上圖當中咱們能夠看到,索引掃描不見了,只有索引查找,彙集索引查找,鍵查找,並且運算符開銷,I/O開銷都下降了不少。索引掃描(Index Scan),彙集索引掃描(Clustered Index Scan)跟表掃描(Table Scan)差很少,基本上是逐行去掃描表記錄,速度很慢,而索引查找(Index Seek),彙集索引查找,鍵查找都至關的快。優化查詢的目的就是儘可能把那些帶有XXXX掃描的去掉,換成XXXX查找。 

這樣夠了嗎?可是回頭又想一想,4000多條數據得3.5秒鐘,仍是有點慢了,應該還能再快點,因此決定再去修改查詢。看看查詢,能優化的也只有那個三個函數了。 

爲了看函數執行效果先刪除索引,看看查詢中函數f_GetAidNamebyConsult1要乾的事情,截取查詢中與該函數有關的子查詢: 

Sql代碼  
  1. select Pub_Consult1.ConsultId,AidName from (select ConsultId,dbo.f_GetAidNamebyConsult1(ConsultId) as AidName  
  2.    
  3. from Pub_Consult1) AidNametb inner join Pub_Consult1  
  4.    
  5. on AidNametb.ConsultId=Pub_Consult1.ConsultId  


獲得下圖的結果: 

 


沒想到就這麼點數據居然要46秒,看來這個函數真的是罪魁禍首。 

該函數的具體代碼就不貼出來了,並且該函數裏面還欠套的另一個函數,自己函數執行起來就慢,更況且還函數裏子查詢還包含函數。其實根據幾相關聯的表去查詢幾個字段,而且把一個字段的值合併到同一行,這樣不必用函數或存儲過程,用子查詢再加sql for xml path就好了,把該函數改爲以下查詢: 

Sql代碼  
  1. with cte1 as  
  2.    
  3. (  
  4.    
  5.     select A.AdapterAssessmentId,case when B.AidName is null then A .AidName else B.AidName end AidName  
  6.    
  7.     from Report_AdapterAssessment_Aid as A left join Pub_ProductDir as B  
  8.    
  9.     on A.ProductDirAId=B.ProductDirAId  
  10.    
  11. ),  
  12.    
  13.  cte2 as  
  14.    
  15. (  
  16.    
  17.       
  18. --根據AdapterAssessmentId分組併合並AidName字段值  
  19.    
  20.     select AdapterAssessmentId,(select AidName+',' from cte1  
  21.    
  22.                               where AdapterAssessmentId= tb.AdapterAssessmentId  
  23.    
  24.                               for xml path(''))as AidName  
  25.    
  26.     from cte1 as tb  
  27.    
  28.     group by AdapterAssessmentId  
  29.    
  30. ),  
  31.    
  32. cte3 as  
  33.    
  34. (  
  35.    
  36.     select ConsultId,LEFT(AidName,LEN(AidName)-1) as AidName  
  37.    
  38.     from  
  39.    
  40.     (  
  41.    
  42.        select Pub_Consult1.ConsultId,cte2.AidName from Pub_Consult1,Report_AdapterAssessment,cte2  
  43.    
  44.        where Pub_Consult1.ConsultId=Report_AdapterAssessment.ConsultId  
  45.    
  46.        and Report_AdapterAssessment.AdapterAssessmentId=cte2.AdapterAssessmentId  
  47.    
  48.        and  Report_AdapterAssessment.AssessTuiJian is null  
  49.    
  50.     ) as tb)  


這樣查詢出來的結果在沒有索引的狀況下不到1秒鐘就好了。再把主查詢寫了: 

Sql代碼  
  1. select distinct  Pub_AidBasicInformation.AidBasicInfoId,  
  2.    
  3.        Pub_AidBasicInformation.UserName,  
  4.    
  5.        Pub_AidBasicInformation.District,  
  6.    
  7.        Pub_AidBasicInformation.Street,  
  8.    
  9.        Pub_AidBasicInformation.Community,  
  10.    
  11.        Pub_AidBasicInformation.DisCard,  
  12.    
  13.        Pub_Application.CreateOn AS AppCreateOn,  
  14.    
  15.        Pub_User.UserName as DepartmentUserName,   
  16.    
  17.        Pub_Consult1.ConsultId,  
  18.    
  19.        Pub_Consult1.CaseId,  
  20.    
  21.        Clinicaltb.Clinical,  
  22.    
  23.        cte3.AidName,  
  24.    
  25.        Pub_Application.IsUseTraining,  
  26.    
  27.        Pub_Application.ApplicationId,  
  28.    
  29.        tab.num  
  30.    
  31. from   Pub_Consult1  
  32.    
  33. INNER JOIN Pub_Application ON Pub_Consult1.ApplicationId = Pub_Application.ApplicationId  
  34.    
  35. INNER JOIN Pub_AidBasicInformation ON Pub_Application.AidBasicInfoId = Pub_AidBasicInformation.AidBasicInfoId                                                             
  36.    
  37. INNER  JOIN(select ConsultId,dbo.f_GetClinical(ConsultId) as Clinical  
  38.    
  39.             from Pub_Consult1) Clinicaltb on Clinicaltb.ConsultId=Pub_Consult1.ConsultId  
  40.    
  41. left join (select distinct ApplicationId, sum(TraniningNumber) as num from dbo.Review_Aid_UseTraining_Record  
  42.    
  43.            where  AidReferralId is null  
  44.    
  45.            group by  ApplicationId) tab  
  46.    
  47.            on tab.ApplicationId=Pub_Consult1.ApplicationId  
  48.    
  49. left JOIN cte3 on cte3.ConsultId=Pub_Consult1.ConsultId                                
  50.    
  51. LEFT OUTER JOIN Pub_User ON Pub_Application.ReviewUserId = Pub_User.UserId  
  52.    
  53.            where Pub_Consult1.Directory = 0  
  54.    
  55. order by Pub_Application.CreateOn desc  


這樣基本上就完事了,在沒有創建索引的狀況下須要8秒鐘,比沒索引用函數仍是快了27秒。 

 


把索引放進去,就只需1.6秒了,比創建索引用函數而不用子查詢和sql for xml path快了1.9秒。 

 


查詢裏面還有個地方用了函數,估計再優化下還能提升執行效率,由於時間有限再加上篇幅有點長了,在這裏就很少講了。 

最後作個總結吧,查詢優化不外乎如下這幾種辦法: 

1:增長索引或重建索引。一般在外鍵,鏈接字段,排序字段,過濾查詢的字段創建索引,也可經過數據庫引擎優化顧問提供的信息去建索引。有時候當你建立索引時,會發現查詢仍是按照索引掃描或彙集索引掃描的方式去執行,而沒有去索引查找,這時極可能是你的查詢字段和where條件字段沒有所有包含在索引字段當中,解決這個問題的辦法就是多創建索引,或者在建立索引時Include相應的字段,讓索引字段覆蓋你的查詢字段和where條件字段。 

2:調整查詢語句,前提要先看懂別人的查詢,搞清楚業務邏輯。 

3:表分區,大數據量能夠考慮。 4:提升服務器硬件配置。

相關文章
相關標籤/搜索