分页查询中的问题

背景

咱们项目中常常会遇到数据库分页查询的场景,如查看用户的历史订单、查看用户的联系人列表等。通常在用户全量数据不可控的时候,咱们都会考虑经过分页的方式来获取数据。一方面数据库查询性能能够获得保证,另外一方面也能够减小客户端数据的传输。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条数据来肯定当前是否还有下一页数据以及下一页的起始位置;

相关文章
相关标签/搜索