阿飛Javaer,轉載請註明原創出處,謝謝!
分頁性能分析
性能瓶頸
查詢偏移量過大的分頁會導致數據庫獲取數據性能低下,以MySQL爲例:
SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10
這句SQL會使得MySQL在無法利用索引的情況下跳過1000000條記錄後,再獲取10條記錄,其性能可想而知。而在分庫分表的情況下(假設分爲2個庫),爲了保證數據的正確性,SQL會改寫爲:
SELECT * FROM t_order ORDER BY id LIMIT 0, 1000010
即將偏移量前的記錄全部取出,並僅獲取排序後的最後10條記錄。這會在數據庫本身就執行很慢的情況下,進一步加劇性能瓶頸。因爲原SQL僅需要傳輸10條記錄至客戶端,而改寫之後的SQL則會傳輸1000010*2的記錄至客戶端。
Sharding-JDBC的優化
Sharding-JDBC進行了2個方面的優化。
首先,Sharding-JDBC採用流式處理 + 歸併排序的方式來避免內存的過量佔用。Sharding-JDBC的SQL改寫,不可避免的佔用了額外的帶寬,但並不會導致內存暴漲。
與直覺不同,大多數人認爲Sharding-JDBC會將1000010*2記錄全部加載至內存,進而佔用大量內存而導致內存溢出。
但由於每個結果集的記錄是有序的,因此Sharding-JDBC每次比較僅獲取各個分片的當前結果集記錄,駐留在內存中的記錄僅爲當前路由到的分片的結果集的當前遊標指向而已。
對於本身即有序的待排序對象,歸併排序的時間複雜度僅爲O(n),性能損耗很小。
其次,Sharding-JDBC對僅落至單分片的查詢進行進一步優化。落至單分片查詢的請求並不需要改寫SQL也可以保證記錄的正確性,因此在此種情況下,Sharding-JDBC並未進行SQL改寫,從而達到節省帶寬的目的。
更好的分頁解決方案
由於LIMIT並不能通過索引查詢數據,因此如果可以保證ID的連續性,通過ID進行分頁是比較好的解決方案:
SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id
或通過記錄上次查詢結果的最後一條記錄的ID進行下一頁的查詢:
SELECT * FROM t_order WHERE id > 100000 LIMIT 10
是否需要這種分頁
無論是SELECT * FROM t_order ORDER BY id LIMIT 0, 100010
或者SELECT * FROM t_order WHERE id > 100000 LIMIT 10
,性能都一般般,後者只是稍微好點而已,但是由於LIMIT的存在,mysql都需要排序;
是否能從產品角度或者用戶習慣等方面解決或者避免這個問題?
- 用戶習慣結合產品需求解決方案:
比如我們以前有個每日TOP榜單需求,分析用戶行爲一般不會無限制往下滑,即使有這種用戶,也是極少數,可以忽略。這樣的話,可以通過SQL
*** LIMIT 300
只查詢10頁總計300個TOP應用,然後把這些數據以list結構保存到redis中。這樣的話,用戶查看每日TOP榜單只需通過LRANGE key start stop
從redis緩存中取數據即可,且限制查詢的offset不允許超過300;