1. 程式人生 > >效能優化總結(三)一對多join的級聯查詢

效能優化總結(三)一對多join的級聯查詢

最近在對一個已經運行了4年的老專案進行效能優化,這是一個app,我主要還是從後臺優化。這個專案後臺提供的API並不多,只有十幾個,但是效能非常差。有些API連每分鐘2000的request都扛不住,雖然後臺的併發node已經增加到8個。一看程式碼就知道為什麼了,原來這些API都需要返回很多的資料,而且是級聯的資料,就像你要獲得一個“軍”的資料,“軍”下面又有“師”,然後是旅團營連排,這樣的樹狀結構,大概有5層,而且是一對多的關係。原來的程式碼中是先獲得第一層的資料,再迴圈獲得下面的第二層,然後是迴圈獲得第三層,依次類推。這樣帶來了很多的資料庫訪問和迴圈操作。結果就是這個API不僅慢而且非常耗CPU。

一開始看到這樣的結構,我是排斥的。。。。一對多如果寫SQL來join的話,就會join出很多的資料來,因為畢竟是相乘的關係。之前就有這種SQL把服務整掛的經驗,當時的情景是用使用者表去join電話表,地址表,eamil表等,一個人可以有多個電話,地址,email。。。所以是一對多的關係,想要一下子全都拿出來,結果在測試環境中測試人員一直往同一個使用者下面加地址,電話等,結果就是這個人有幾百個地址,幾百個電話。。。。最後join出幾百萬條資料,hibernate搞不定這個資料量。

回到這次的問題,怎麼辦,這個是比那個更多的join。首先資料量如果不大的話,我把它寫成一個sql,用很多的join應該是沒有關係的。於是去找了公司的DBE一塊商量,DBE說先用這樣的方式試一下。於是我開始寫SQL,join了七八張表,用MyBatis。開始的自己測試的時候發現還可以,給效能測試一跑就完了,剛開始跑幾分鐘CPU就100%,後來分析了一下果然資料量還是很大的,效能測試的資料是從產品環境拷過來的。就SQL的執行時間而言,並不慢,因為join的地方都有索引,也規避了一些可能造成低效的寫法,主要的問題還是出在CPU時間上。看來在這一點上MyBatis和Hibernate是一樣的,畢竟需要把查詢出來的那麼多資料進行合併再生成bean。分析了其中的一個測試,發現這條查詢可以查出50萬條資料。經過分析,我決定把一個SQL分成兩個,兩次查詢再將結果自己組合,這樣其中一個查詢的資料大概是40條,另外一個12000條。後來又覺得12000條的這個查詢資料也很多,而且可能還有更多的情況,於是分成三次查詢。分的原則就是把那些資料比較多的表互相不要join在一起,12000條就是一個20一個600兩個結果集join的結果。分成三個查詢是結果集變小了,但是帶來新的問題,那就是怎麼把這些資料合併在一起,比如如何把“班”的資料合併到每個“排”,而“排”又在“連的下面”。必須迴圈才行,而迴圈本身又是很耗費CPU的操作。為了減少迴圈的次數,先把“班”的資料進行一次遍歷,放到一個HashMap裡面去,然後遍歷大的陣列,把每個“排”對應的班的List加進去。這樣做能減少一些遍歷次數。尤其在資料量大的時候應該是值得的。

經過這樣的改動之後再上效能測試,我們的壓力目標是壓到8000r/m,在整個測試過程中,response的時間一直很穩定,大概100到200ms,而CPU從10%到60%。由於資料量太大,CPU還是最大的問題。至此我們的目標是達到的,但是這個API是可以繼續優化的,一個新的想法是,這裡用到了3個自定義的TypeHandler,可以考慮把TypeHandler去掉。

這一API的設計本身非常不合理,其實完全沒有必要每次返回那麼多的資料,只是在不改變前端的前提下,我們只能把後端這樣優化。而且就算前端要改,有些使用者可能還在用老的版本,所以優化這樣的API還是有一定的必要的。