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