咱們項目中常常會遇到數據庫分頁查詢的場景,如查看用戶的歷史訂單、查看用戶的聯繫人列表等。通常在用戶全量數據不可控的時候,咱們都會考慮經過分頁的方式來獲取數據。一方面數據庫查詢性能能夠獲得保證,另外一方面也能夠減小客戶端數據的傳輸。java
在數據庫分頁查詢也有些場景須要特別注意,不然會容易遇到坑。mysql
假設有如下場景,咱們須要對每一天的訂單進行統計對帳。訂單order表簡單信息以下表所示。sql
屬性 | 類型 | 備註 |
---|---|---|
id | long | 自增主鍵 |
... | ... | ... |
status | int | 訂單 |
update_time | long | 更新時間 |
由於是對每一天的訂單進行統計,咱們很容易就想到根據訂單更新時間update_time來進行條件過濾。因此基本的查詢語句基本以下所示。數據庫
select * from order_:tableId where update_time>:starTime and update_time<=:endTime and status=:status order by id asc limit :limit
複製代碼
在咱們的服務裏面會分頁獲取訂單列表,直到獲取完全部的訂單記錄。示例代碼以下:性能
@Resource
private OrderDao orderDao;
public void countAllOrders(int tableId, long startTime, long endTime, int limit) {
long tempStartTime = startTime;
while(true) {
List<OrderRecord> orders = orderDao.getOrderByTime(tableId, tempStartTime, endTime, limit);
// 統計訂單...
if(order.size()<limit){
// 當數據條數沒達到指定數量時,說明沒有更多數據了
break;
}
tempStartTime = orders.get(order.size()-1).getUpdateTime();
}
}
複製代碼
初步看mysql查詢語句沒什麼問題,不過再認真思考下可能會發現這個查詢會有隱患,在極端狀況下可能會遺漏數據。spa
假設有幾個訂單的updateTime恰好相等,又剛好處在分頁的邊緣,這個時候就會出現訂單遺漏的狀況。簡化說明這種場景,假設訂單表有如下3條記錄,咱們限定返回條數爲1。code
id | ... | update_time |
---|---|---|
1 | ... | 1552105405000 |
2 | ... | 1552105405000 |
3 | ... | 1552105405001 |
這個時候遍歷訂單數據,會發現咱們遍歷數據的時候會遺漏id=2的記錄。這是由於id=1和id=2的訂單記錄其update_time是相等的。當遍歷完id=1的訂單記錄後startTime已經被設置爲1552105405000,而後就遍歷update_time大於1552105405000的記錄了,這個時候就跳過了id=2的記錄。get
以自增ID來迭代it
這裏會遺漏數據根本緣由是updateTime並非惟一的。咱們能夠考慮使用自增主鍵來進行迭代,對應的查詢語句修改以下。io
-- startTime和endTime僅僅做爲條件範圍限制,經過id來進行分頁迭代
select * from order_:table_id where id>:id and update_time>:starTime and update_time<=:endTime and status=:status order by id asc limit :limit
複製代碼
代碼也作下相應的調整,使用id爲迭代的依據。
@Resource
private OrderDao orderDao;
public void countAllOrders(int tableId, long startTime, long endTime, int limit) {
long id = 0;
while(true) {
List<OrderRecord> orders = orderDao.getOrderByTime(tableId, id, startTime, endTime, limit);
// 統計訂單...
if(order.size()<limit){
// 當數據條數沒達到指定數量時,說明沒有更多數據了
break;
}
id = orders.get(order.size()-1).getId();
}
}
複製代碼
經過自增ID來進行分頁迭代能夠避免數據遺漏,時間等其它因素可做爲篩選的條件。
在不少場景下咱們都須要知道當前分頁是否還有下一頁,在客戶端可能還須要給一個明確的」已經到底「的提示。能夠經過分頁數據是否達到指定數量來判斷是否還有下一頁,不過這種處理方式可能會多一次數據庫查詢(恰好在前一頁返回了最後的數據,卻由於數量恰好等於分頁數而沒辦法判斷)。
多查詢1條數據
其實更簡單的處理方式是多查詢1條數據,這多出來的1條數據就能夠用來判斷有沒下一頁,以及定位下次查詢的起始位置nextOffset。若返回的數據條數等於limit+1,那說明還有下一頁,不然數據讀取已經完成。
特別注意
多查詢1條數據須要注意迭代的起始判斷,應該是包含nextOffset。由於並無將最後一條數據返回給客戶端,只是用於判斷有沒下一頁以及肯定下次拉取的起始位置nextOffset。
一、在分頁查詢的場景裏面儘可能使用不重複的主鍵等來進行迭代;
二、能夠經過多查詢1條數據來肯定當前是否還有下一頁數據以及下一頁的起始位置;