分頁查詢中的問題

背景

咱們項目中常常會遇到數據庫分頁查詢的場景,如查看用戶的歷史訂單、查看用戶的聯繫人列表等。通常在用戶全量數據不可控的時候,咱們都會考慮經過分頁的方式來獲取數據。一方面數據庫查詢性能能夠獲得保證,另外一方面也能夠減小客戶端數據的傳輸。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條數據來肯定當前是否還有下一頁數據以及下一頁的起始位置;

相關文章
相關標籤/搜索