SQL Server SQL性能優化之--通過拆分SQL提高執行效率,以及性能高低背后的原因


 

復雜SQL拆分優化

  拆分SQL是性能優化一種非常有效的方法之一,   具體就是將復雜的SQL按照一定的邏輯逐步分解成簡單的SQL,借助臨時表,最后執行一個等價的邏輯,已達到高效執行的目的   一直想寫一遍通過拆分SQL來優化的博文,最近剛好遇到一個實際案例,比較有代表性,現分享出來,   我們來通過一個案例來分析,為什么拆分語句可以提高SQL執行效率,更重要的是弄清楚,拆分前為什么慢,拆分后為什么快了?   幼稚的話,各位看官莫笑

  

先看一下相關表的數據量,大表也有5900多萬,小表有160多萬 (聲明:我從來沒認為5000W就是大表,或者說表很大就說明業務或者數據庫很牛叉,從來么有。能把大表拆分小表,永遠不出現超級大表又能滿足業務需求,那才叫牛逼)

   

 

  如下是本次優化的SQL語句   其實SQL稱不上復雜,無奈這幾個表的數據量都稍微顯得有些大,另外里面嵌套比較復雜的業務邏輯,   歷史上經過幾輪“高手”的在索引上全方為的優化之后,也能正常運行   但是隨着時間的推移,表中的數據量越來越大,溫水煮青蛙一般,SQL越來越慢,越來越慢,   終於還是暴露了出來,性能問題還是無法被掩蓋的,   說實話這么個SQL,分頁查詢運行超過1分鍾(服務器比較穩定,沒有什么負載,測試之前博主習慣性rebuild所有索引)

  造成上述問題的原因是多樣的,業務上的,歷史上的,數據上的等等吧,也不用太鄙視了吧,哈哈   家家有本難念的經,其實不用笑,之前有個同事離職去了一家挺牛逼的上市公司,又一次發微信說卧槽這里的系統還真不如咱們在**公司的系統的,哈哈   博主所在的公司,也有數千台SQL Server數據庫服務器了,動不動超過一兩分鍾分鍾的查詢還是有一些的,   這也是博主能夠專職長期優化SQL的原因吧,因為這種SQL遇到太多了,歷任開發人員和DBA也不是吃白飯的,想通過索引來實現質的改變是不可能的   並不是我不重視索引,或者說我不懂索引,   我覺得僅僅是通過索引就能優化的SQL語句,或者說建了索引,速度立馬上去了幾十倍,那只能說明一個問題:這種問題本身就太弱。

  當第一次看到這個SQL執行的這么慢,在了解相關表數據之后,   第一感覺能否通過拆分,減小SQL連接條件,查詢條件的復雜程度,然后再跟其他表join產生最后的結果集,

  但是如何拆分?先拆分哪個表?怎么組合?這才是問題的本質   舉個簡單的例子   比如下面一個查詢語句,有四張表join,有多個查詢條件,連接條件等等

復制代碼
select A.colName,B.colName,C.colName,D.colName from TableA A inner join TableB B on A.Id=B.Id and A.Type=B.Type and 其它條件 inner join TableC C on B.Code=C.Code and 其它條件 inner join TableD D on C.BusinessId=D.BusinessId and 其它條件 where A.BusinessDate>=Date1 and A.BusinessDate<=Date2 and A.BusinessStatus='' and B.BusinessDate='' and C.BusinessDate='' and 其他查詢條件 and 其他查詢條件 order by col1,col2,col3 OFFSET M ROWS FETCH NEXT N ROWS ONLY
復制代碼

如果是三個表拆分,跟第四個表join,可以通過如下備選方案 可以把ABC join起來加上對應的查詢條件,拆分成一個臨時表,然后跟D表join, 可以把ABD join起來加上對應的查詢條件,拆分成一個臨時表,然后跟C表join, 可以把ACD join起來加上對應的查詢條件,拆分成一個臨時表,然后跟B表join, 可以把BCD join起來加上對應的查詢條件,拆分成一個臨時表,然后跟A表join, 這里就有一個小技巧,要觀察一下三個表加上對應的查詢條件結果集的總行數, 比如ABC join是3000條結果集,這3000條結果集跟D表join產生了20w條結果,那么就可以先排除D表, 讓ABC join起來加上對應的查詢條件,生成臨時表,在臨時表上建立合理的索引,再跟D表join 也就是說先排除一些產生大結果集的join參與join,其他的表join,得到一個相對較少的臨時結果集, 在臨時結果集上建立索引,再用這個臨時結果集去join其他的表。

這種拆分方式,還有最重要的一步,在臨時表上加合適的索引,以最優化臨時表與物理表的執行 如果數據量不大,拆分是適得其反的,完全沒有必要,但是在數據量越大的時候,效果越明顯, 那么這里的拆分后究竟有多明顯的效果? 記得之前是多少秒?1分鍾3秒,也就是63秒,這里是2秒鍾 說實話,這種拆分方式經常用,說實話這個速度的提示是我沒有想到的

 

 

 

其實問題到這里才剛剛開始

  為什么拆分之前那么慢,為什么拆分之后又變得這么快?   執行計划就不細看了,上文說了,這個查詢並不缺少索引,也確實用到了索引,但是並不代表,有了索引,用到了索引,就萬事大吉了。   因為查詢條件較為復雜,相關的表建立的是復合索引,如果要說索引,就必須說統計信息(statistics),   對於復合索引,也即兩個以上字段的索引,其統計信息的特點是只會維護第一個字段的直方圖信息,   這就決定了SQL Sever在對數據量做預估的時候,有可能出現誤差   我這里有寫統計信息相關的知識的,可以參考   某些多個查詢條件的情況下,即便是用到了復合索引,   SQL Server並不能准確地預估某些條件下數據的行數,如果SQL Server一開始就錯誤地預測到預期的數據量很小,   那么后繼每一步都無法准確地預測真正數據的大小,也即第一步就錯了,導致后面每一步都受到第一步的干擾,   后面往往會采用Loop join的方式執行,這種方式對於較小的結果集,當然沒有問題,如果遇到較大的結果集,就非常低效了   (見過很多超級復雜,join的表多,很復雜的查詢條件,且運行緩慢的SQL,SQL Server往往是以loop join的方式去處理表之間)

  所以我們先拆分出來一個較小的結果集,存放在臨時表,   在第一步的拆分過程中,即便某些情況下無法正確地預估表的行數,因為結果集比較小,采用了Loop join的方式來處理也是沒有問題的。   一旦我們拆分出來一個臨時表,對臨時表加上合理的索引,再跟其他的大表join,   由於SQL變得簡單了,加上有索引,往往會以高效的方式去執行,性能也就上去了   那么什么是高效?是不是主觀臆斷或者說是猜測,比如呢?   比如通過更大的內存授予(Memory Grant),因為能更加准確地預估到每一步的行數吧,采用並行運行(這里有寫並行相關的,可參考)等等,獲取更多的資源從而提高執行效率   事實上,本文舉例的SQL拆分之后的運行,正式因為此,授予更大的內存+並行,才得以高效執行。如圖。

  

 

總結:

  本文通過一個SQL語句的拆分來達到優化的方法,說明在一定情況下,拆分SQL是優化的可選方案。   當然也不是說,復雜的SQL一定會執行的慢,一定需要拆分,對於多個大表join,如果邏輯簡單,可能也會快速的執行   但是對於那些多個大表join的SQL,尤其是在連接條件,查詢條件,索引信息復雜的情況下,如果出現性能問題,可以考慮通過拆分SQL來優化其執行效率   這個只能說,執行的慢的SQL,通過具體的分析,是可以通過拆分SQL語句生成臨時表來解決的。   SQL 拆分解決了性能問題,但,更重要的是,一定要弄明白:慢,是為什么慢,快,是為什么快,弄不清楚的話,類似問題還會時不時地讓你感到困惑。   理解了本質,才能夠游刃有余,更好地掌握SQL Server。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM