­

場景之分頁查詢設計

一、場景

後端開發中,無論是管理後台還是面向普通用戶,必不可少的涉及到批量數據的查詢,這種查詢的結果通產在前端以列表分頁的形式展示出來,包含的參數包括,頁碼pageNum,每頁記錄數pageSize或者說是頁面大小,總共多少條記錄total等。

其中前端傳入參數的時候可能還包含,日期範圍startTime,endTime,以及涉及到根據商品名的模糊匹配等,還有包括按照篩選欄位正序倒序排序等。

二、實現

由於篩選條件通常是多種多樣的,並且有些數據更新頻繁,很難指定有效的快取策略,即使有命中率以及使用率也很低,所以放棄快取策略,壓力直接給到了資料庫這邊。

1、後台sql

操作映射到後台,可能就是查詢語句比如

select * from table_name where create_time >= startTime and create_tiem <= endTime and product_name like %product% order by id desc limit pageNum offset pageSize *  (pageNum - 1)

這裡默認前端傳參pageNum頁數是0,所以在參數校驗的時候,可以對頁碼重置,保證偏移量的正確。

if request.PageNum == 0:
    pageNum = 1

根據頁碼和頁面大小可以計算出正確的偏移量

offset = pageSize * (pageNum - 1)

2、limit和offset

執行sql的時候可以用offset,limit或者單獨limit做分頁。
比如

1. select* from table LIMIT 1,3
2.select * from table LIMIT 3 OFFSET 1

上面兩種寫法都表示取2,3,4三條條數據。

  • 1中,limit後邊跟兩個參數,第一個數表示要跳過的數量,後一位表示要取的數量。 LIMIT 1,3 就是跳過1條數據,從第2條數據開始取,取3條數據,也就是取2,3,4三條數據
  • 2中,limit和offset組合使用的時候,limit後面只能有一個參數,表示要取的的數量,offset表示要跳過的數量。LIMIT 3 OFFSET 1 表示跳過1條數據,從第2條數據開始取,取3條數據,也就是取2,3,4三條數據。

3、問題

以上採用limit和offset組合的方式區分偏移量和頁面大小。
實例中只是簡單的多參數查詢,如果涉及到的表記錄數很多,需要逐行遍歷,特別是在用戶端顯示的時候,如果用戶從第一頁直接請求最後一頁,先忽略查詢條件,假設每頁20條,到後端的表現可能就是

select * from table_name order by product_id offset 1000000 limit 20

採用這種SQL查詢分頁的話,從100萬數據中取出這20行數據的代價是非常大的,需要先查出前1000020條記錄,根據product_id排序,然後拋棄前面1000000條。效率很低。

三、優化

所以想到對於資料庫的只是查詢操作,對錶建立索引進行優化。

1、覆蓋索引

如果對於sql查詢的時候只要查找表中的部分欄位,可以通過索引可以直接獲取查詢的結果,不再需要回表查詢,也就是建立覆蓋索引。

select product_id, product_name from table order by product_id offset 1000000 limit 20

對product_id 和product_name兩個欄位建立聯合索引,查找的時候直接命中不會再去主鍵索引樹找真實的記錄。

2、子查詢優化

因為實際開發中,只展示查詢一兩列欄位操作是非常少的,因此上述的覆蓋索引的適用範圍就比較有限。
而子查詢避免了大量查詢偏移量中的記錄,而是只查詢目標記錄的Id,進而再去查詢所有欄位。

select * from table where id>=(select id from table order by proudct_id limit 1000000, 1) limit 20;

這種子查詢也有自己的限制,

  • 要求主鍵ID必須是連續的,但是一般的業務中,可以定義默認主鍵或者自定義字自增主鍵,在刪除的時候,採用邏輯刪除(is_del=1)而非物理刪除,對於新增的記錄的可以實現主鍵連續。
  • where子句不允許再添加其他條件,但是可以在前端入參的時候做校驗,對於默認參數的分頁查詢可以使用子查詢優化。

3、記錄查詢位置

用戶可能觸發相同查詢條件的多次分頁查詢,比如第一次

select * from table where product_id > 2222 offset 500000 limit 10;

經過此次查詢之後後端記錄下來商品id>2222的第500000條記錄的id,如果在有類似查詢如下

select * from table where product_id > 2222 offset 1000000 limit 10;

分頁時直接從記錄的這個500000條記錄的id的位置開始掃描,從而避免MySQL掃描大量的數據再拋棄的操作。