四種不同的分頁解決方案,你學會了嗎?
”使用mysql limit 分頁就行了,分頁查詢用得著四種寫法嗎? "
這可能是很多人的想法。的確mysql limit offset是可以勝任分頁的,但是另外三種辦法在其他場景表現更好。
大家最熟悉的就是如下的分頁截圖,返回總頁數、支持頁數跳轉。
圖片
1 Limit Offset分頁
例如每頁10條,查詢第三頁 ,mysql limit 部分為:limit 20,10;
前段每次需要指定 每頁數量,當前頁數。由后端拼接查詢SQL,構建mysql limit 子句。
limit offset 分頁有幾個特性。
- 支持頁數跳轉。用戶選定第幾頁,就跳轉到對應的頁面。
- 返回記錄總條數。用戶可以看到共幾頁,一共多少條數據。
limit offset 實現簡單,但是存在缺陷。當出現深度分頁時,MySQL 需要掃描大量數據才能找到指定頁的數據,造成慢查詢 ,增加增加數據庫的內存和cpu負載, 如果這個深度分頁的QPS比較高,無疑最終會拖垮數據庫。在流量高峰期,如果深度分頁的慢查詢較多,毫無疑問,會增加其他SQL耗時,影響其他業務場景。
值得說明的是,分頁查詢必須指定排序方式。如果沒有指定排序方式,使用分頁很難保證數據不會出現重復。 如果實在沒有排序字段,可以使用主鍵ID。
我曾經犯過類似錯誤,在使用ElasticSearch替換lucene 做檢索時,發現lucene和ElasticSearch返回的結果一直不一致,排查了很久,才意識到必須指定排序方式,否則使用分頁查詢會導致數據重復。
那么Limit Offset就沒有其他方式避免深度分頁嗎?答案是可以
2 Limit 指定主鍵Id過濾
如果在查詢條件上加上主鍵Id是不是就可以了呢?
改進前:
select * from students where xxxx查詢條件xxx order by id desc limit 1000,20;
改進后:
select * from students where xxxx查詢條件xxx AND id <lastMinId order by id desc limit 20;
改進后在原有的查詢條件上 指定了lastMinId,上一輪最小的Id。在查詢下一頁時,把上一頁的最小id 傳下去,這樣保證后續查到的列表都是小于lastMinId。從源頭上增加了查詢條件,減少了mysql的檢索范圍,每次都只獲取前二十條數據。
這樣就高枕無憂了嗎?當然不
這種方式前提條件是排序方式可以指定主鍵Id,如果根據其他排序方式,就不能這樣做了。
這種方式還有其他應用場景嗎?最佳的場景就是從下游批量獲取大量數據時,可以根據主鍵id進行排序,每次選擇最大的N條,或最小的N條。
每次查詢都更新主鍵id范圍,這樣就能避免深度分頁,查詢全部的數據。
3 HasMore 滾動查詢
有的業務場景例如用戶App端的購買記錄頁,用戶只能每頁滾動查詢購買記錄,無需知道購買訂單總數。針對這個場景,有什么優化呢?
在之前的limit Offset分頁時,需要返回記錄總數,前端也要確定查詢總頁數。滾動分頁查詢則無需獲取總頁數,無需查詢總數。減少了一次select count(*)的查詢。
只需要在每一次分頁查詢時,每頁數量+1 即可。例如每頁10條,可以指定11條,如果真查出來11條,hasMore=true,上游需要繼續查,否則hasMore=false,上游無需再分頁查詢。
4 ElasticSearch 分頁查詢
ES 比較適用于檢索條件復雜、實時性要求比較低的查詢場景。例如B端的各類復雜查詢條件檢索場景以及 C端用戶關鍵詞訂單列表搜索等場景。查詢耗時基本在100ms以上、甚至1s以上。
值得一提的是需要mysql數據異構到ES,ES加載進索引也有1s左右延遲,數據從產生到ES索引延遲比較高。
ElasticSearch 支持分頁查詢,和Mysql Limit offset 類似。同時也強烈建議,使用分頁查詢時,指定排序方式。
SearchRequest searchRequest = new SearchRequest(index);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//計算出記錄起始下標
int from = (pageNum - 1) * pageSize;
// 起始記錄下標,從0開始
sourceBuilder.from(from);
//每頁顯示的記錄數
sourceBuilder.size(pageSize);
和mysql類似,ES也有深度分頁的查詢壓力,默認的最大查詢深度max_result_window=1W, 閾值可以修改。在低頻的B端查詢場景,可以根據需要適當調整閾值。
以上4種分頁查詢方式沒有最好,需要針對不同的場景選擇最合適的。