在開發API的時候,有時候數據太多了,就需要分頁讀取。
基於偏移量的分頁(Offset-based)
這種方式就是會提供一個每頁筆數(page size)來定義返回條目的最大數,提供一個頁數(page number)來表示從哪里開始讀取數據。
例如:
SELECT * FROM "CampusResumes" ORDER BY "Name" DESC LIMIT 5 OFFSET 10;
這句話的意思就是從該表中讀取數據,按照Name字段降序排序,從第10筆數據后開始讀取,一共讀取5筆(可能不足5筆)。
這就相當於page size = 5,page number = 3的分頁讀取。
Offset-based分頁方式實現起來非常的簡單,對用戶來說體驗也比較好。但是還有有一些劣勢的:
- 對於大規模的數據集,效率不夠高。因為數據庫需要進行count和skip操作。
- 如果數據經常發生變化,那么結果不可信。在查詢的時候如果插入或刪除了數據,那么某條數據可能會出現兩次或者翻頁的時候越界了。
- 在分布式系統中實現起來略麻煩。這種情況下,你可能需要掃描不同的數據碎片,然后才能得到想要的數據。
總體來說,當允許結果出現誤差的時候,Offset-based分頁還是很好用的。
基於游標的分頁(Cursor-based)
為了解決Offset-based分頁的那些問題,可以采用Cursor-based分頁。
這種方式是這樣的:客戶端首先發送請求,請求里提供所需數據的數量。然后服務器響應請求,返回這些數量的數據(如果有這么多數據的話),同時還會返回一個游標(Cursor)。在下一次請求中,客戶端除了發送請求數據的數量之外,還把這個cursor也傳送過去,這個cursor就表示這次所要讀取的數據的開始位置。
這看起來和Offset-based分頁差別不大,但是卻更有效率。數據庫里面的數據可以根據cursor值來獲取。
例如:
SELECT * FROM "CampusResumes" WHERE "Id" > 15 ORDER BY "Id" LIMIT 5;
這個例子里,上次請求返回的cursor(Id字段)值為15,這次要獲取Id比15大的連續的5條數據。
這里的Id字段本身就是一個索引,所以查詢起來非常快。
在這次請求的響應里,可以把本次結果的最后一條的Id作為cursor再返回去:
所以返回的cursor值為23,以供下次讀取。
Cursor-based翻頁的優點是:
- 性能好。因為cursor字段通常都是索引列,查起來很快。
- 一致性。添加和刪除數據並不影響返回的結果,翻頁時同一筆數據也只會被返回一次。
Cursor-based翻頁通常適用於大量和動態的數據集,但是它也有一些缺點:
- 無法跳轉到指定的頁。Cursor-based翻頁只能一頁一頁遍歷結果。
- 結果必須基於一個唯一並且順序的字段。不可以讓添加記錄到任意位置。
- 實現起來比Offset-based復雜一點,尤其對客戶端來說。
對於Cursor字段的選擇:
- Id,順序的主鍵。
- 時間戳。
- 加密字符串。它們看起來像隨機字符串,但實際上通常是Cursor里加入了額外的信息。
總體來說Cursor-based翻頁還是更適合於高吞吐的應用,這種情況下客戶端通常需要掃描整個數據集。
翻頁的最佳實踐
- 設定每頁的最大筆數限制。
- 針對大數據集,盡量不要使用Offset-based分頁。
- 分頁的默認排序,通常會把新的數據先返回,舊的數據往后翻。
- 沒分頁的API盡量去實現分頁。
- 分頁的時候,最好把下一頁的鏈接一同返回,並鼓勵客戶端使用這個鏈接,參考HATEOAS。這樣以后你改變翻頁策略的時候,客戶端不會爆掉。
- 不要在Cursor里加入敏感信息。