當我們展示一個列表中的內容時,難免會遇到分頁問題,因為列表中的內容數量可能很多,但是使用者能一次看到的介面大小是有限的,不可能一個介面展示所有的內容,從後端一次性取太多的資料也會給後端造成額外的壓力。
一般來說,儲存在Mysql中的資料我們有兩種分頁方式:基於id的分頁和基於offset的分頁。
下面以我在實際生產環境中遇到的一個sql為例,相關欄位因為保密問題均用A、B、C……代替。
SELECT *FROM table_A USE INDEX (index_A)WHERE A = xxx AND B = xxx AND C IN (xxx)ORDER BY D DESCLIMIT 33380, 11KEY `index_A` (`A`,`B`,`D`,`C`)
mysql使用offset分頁的時候會首先去二級索引(index_A)中找出滿足條件的offset+limit行記錄的id,然後根據id去聚簇索引中找到對應的行記錄,取出offset+limit行資料,最後丟掉offset行,只保留limit行,這樣效率在offset很大的時候很差,因為去聚簇索引中訪問了太多不必要的資料。
上面這個語句的執行時間大概是8s,而id分頁和上面有什麼不同呢。id分頁就是每次去後端請求資料時,帶上前一頁最後一個id,這樣的話我們在去mysql撈資料的時候where條件裡加上一個id>last_id,limit 33380, 11換成limit 11,按照id來order_by。這樣可以少去聚簇索引中拿很多資料。上面的例子就可以少拿3w多條,只拿需要的11條。
id分頁的限制。
id分頁往往需要在產品上做一些妥協,因為如果每次拿資料都是基於上一頁最後一個id的話,我們就無法進行指定頁的跳轉,例如直接跳到第5頁等等,載入資料的時候使用的是更多按鈕。
如果上述sql所在服務改成id分頁的話需要改產品分頁設計,前端可能也要調整,改動會大一點。有一種消極式載入的方式,可以解決offset過大導致的分頁效能問題。
我們將上述例子中的sql拆成兩句:
1.SELECT table_A.idFROM table_A USE INDEX (index_A)WHERE A = xxx AND B = xxx AND C IN (xxx)ORDER BY D DESCLIMIT 33380, 11;2.Select * from table_A where id in (ids)
兩個查詢加起來時間大概50ms,相比之前的8s,快了很多,原因如下:
第一個查詢會走覆蓋索引,因為所有查詢的列都在二級索引(index_A)中,不用去訪問聚簇索引中的資料行,這樣效率是很快的,我們可以拿到我們需要的11個記錄的id,之後第二個sql根據id去聚簇索引中拿資料也很快。