PageHelper-Cursor:支持游标分页的 PageHelper 修改版,解决 MyBatis 深分页性能问题
在 MyBatis 项目中,PageHelper是常用的分页插件,它默认采用 LIMIT offset size`的传统物理分页方式 。这种分页很容易在大数据量表中触发深分页性能问题:当 pageNum 越来越大,SQL 执行性能呈指数级下滑,offset 越大扫描代价越高。
举个例子:
sql
SELECT * FROM user ORDER BY id LIMIT 1000000, 20;
这种 SQL 将会导致 MySQL 扫描百万级数据后再丢弃数据,拖垮查询性能。
那么有没有更优雅、更高性能的分页方式?
答案是:有!那就是"游标分页"(Cursor Pagination)。
PageHelper-Cursor 是什么?
PageHelper-Cursor 是基于 PageHelper 6.1.1 改造的版本,增加了 游标分页能力 ,通过一个有序索引字段充当分页游标 ,绕开了 OFFSET 带来的扫描成本,从而高效支持深分页。
github链接,文档齐全:Cursor-PageHelper: Mybatis通用分页插件
✅ 特性总结:
| 特性 | 支持 |
|---|---|
| 避免深分页性能问题 | ✅ |
| 兼容 PageHelper 使用体验 | ✅ |
| 支持 MySQL & PostgreSQL | ✅ |
| 支持字段游标分页 | ✅ |
| 使用简单 | ✅ |
🚨 注意:该版本目前为非常早期的测试版,性能和可靠性无法保证,强烈建议不要用于生产环境。
为什么游标分页能解决深分页?
游标分页的核心思想:
不再跳过前面所有数据,而是根据上一个查询返回的最后一个字段作为游标继续往后查。
例如:
sql
-- 第一次查询
SELECT * FROM user WHERE id > 0 ORDER BY id LIMIT 20;
-- 下一次查询,从最后一个 id 开始继续
SELECT * FROM user WHERE id > 1020 ORDER BY id LIMIT 20;
这种做法依赖索引查找,不会扫全表,性能几乎是常数级的 O(1)。
引入 PageHelper-Cursor
如果你的项目是 Spring Boot + MyBatis,在本地部署打包后直接加入下面依赖即可:
xml
<!--PageHelper SpringBoot 整合-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper-spring-boot-starter.version}</version>
</dependency>
<!--PageHelper Cursor 测试版-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>6.1.1-cursor-SNAPSHOT</version>
</dependency>`
⚠️ 环境要求:最好是MyBatis 3.1.0+
使用示例
✅ 例1:基于自增 ID 分页(最简单)
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired private UserMapper userMapper;
@GetMapping public PageInfo<User> getUsers(
@RequestParam(required = false, defaultValue = "0") Long cursor) {
// 使用游标分页
PageHelper.startCursor("id", cursor, 20);
List<User> users = userMapper.selectAll();
return new PageInfo<>(users);
}
}
前端分页请求示例:
javascript
GET /api/users?cursor=0 GET /api/users?cursor=1001
✅ 例2:使用 create_time 游标分页(更通用)
java
@GetMapping("/listWithCursor")
@Tag(name = "获取所有评论信息", description = "管理员分页获取当前所有评论信息列表")
public Result<List<TopCommentVo>> queryWithCursor(
@RequestParam(required = false) String keyWord,
@RequestParam(defaultValue = "0") String lastDate,
@RequestParam(defaultValue = "10") int pageSize) {
if(lastDate.equals("0")){
lastDate = null;
}
// 开启分页
PageHelper.startCursor("c.create_time",lastDate,pageSize,false); // 执行查询
List<TopCommentVo> list = commentService.query(keyWord);
// 获取分页信息
PageInfo<TopCommentVo> pageInfo = new PageInfo<>(list);
// 清理分页
PageHelper.clearPage();
// 使用自定义分页返回方法
return Result.page(list, pageInfo.getTotal());
}
注意事项(必看❗)
✅ 成功使用游标分页必须满足几个条件:
| 条件 | 必要性 |
|---|---|
| 游标字段必须参与排序,且是第一个排序字段 | ✅ 必须 |
| 游标字段必须有索引支持 | ✅ 强烈推荐 |
| 暂不支持任意表达式分页 | ✅ 不支持 |
| SQL 如有 ORDER BY 必须与游标字段一致 | ✅ 必须 |
与传统分页对比
| 对比项 | LIMIT 分页 | Cursor 分页 |
|---|---|---|
| 深分页性能 | ❌ 极差 | ✅ 稳定 |
| 查询效率 | O(N) | O(1) |
| 依赖 OFFSET | ✅ 是 | ❌ 否 |
| 是否要求索引 | ✅ 推荐 | ✅ 强制 |
| 数据一致性好 | ✅ | ⚠️ 依赖排序字段 |
总结
PageHelper-Cursor为 MyBatis 提供了高性能分页的新选择,在处理海量分页时性能优势非常明显。它特别适合:
✅ 评论流、订单列表
✅ Feed 流、用户动态
✅ 日志查询、时间排序类数据
目前项目处于测试阶段,如果你对性能敏感、希望替换掉 LIMIT 深分页,相信这个项目会对你有帮助。