MyBatis分页查询与性能优化实践
分页查询是后台系统最常见的功能之一。列表页、搜索页、管理后台、日志查询都会用到分页。MyBatis 中分页实现并不复杂,但如果 SQL、索引和分页方式设计不当,数据量大后会出现查询变慢、数据库 CPU 升高和接口超时。
本文介绍 MyBatis 分页查询的基本写法、总数查询、深分页问题和常见优化方法。
基础分页 SQL
MySQL 中常见分页写法:
1 | select id, username, email, created_at |
offset 的计算:
1 | int offset = (pageNo - 1) * pageSize; |
Mapper 接口:
1 | public interface UserMapper { |
XML:
1 | <select id="selectPage" resultType="com.example.UserDO"> |
Service 组装结果:
1 | public PageResult<UserVO> pageUsers(Integer status, int pageNo, int pageSize) { |
参数边界校验
分页参数必须限制范围:
1 | pageNo = Math.max(pageNo, 1); |
不要允许前端传 pageSize=100000。这会导致数据库扫描大量数据,也可能让接口返回过大 JSON。
索引设计
分页 SQL 的性能很依赖索引。对于:
1 | where status = ? |
可以考虑联合索引:
1 | create index idx_users_status_created_at on users(status, created_at); |
索引设计要结合真实查询条件。如果还有租户字段:
1 | where tenant_id = ? |
索引可能需要调整为:
1 | create index idx_users_tenant_status_created on users(tenant_id, status, created_at); |
使用 explain 查看执行计划:
1 | explain |
重点关注是否使用索引、扫描行数和是否出现 Using filesort。
避免 select *
分页列表通常只需要展示部分字段。不要写:
1 | select * |
应该只查询页面需要字段:
1 | select id, username, email, created_at |
字段越多,IO、网络传输和对象映射成本越高。大字段如简介、配置 JSON、图片内容更不应该出现在列表查询中。
深分页问题
当页码很大时:
1 | limit 1000000, 20 |
数据库需要跳过前面 1000000 行,再返回 20 行,性能会明显下降。
如果业务允许,可以使用游标分页,也叫基于上一页最后一条记录的分页:
1 | select id, username, email, created_at |
第一页不传 lastCreatedAt,下一页传上一页最后一条记录的 created_at。
如果排序字段可能重复,建议加上 id 作为稳定排序:
1 | select id, username, email, created_at |
对应索引:
1 | create index idx_users_status_created_id on users(status, created_at, id); |
游标分页不能直接跳到任意页,但非常适合信息流、日志列表和移动端下拉加载。
count 查询优化
count(*) 在大表上也可能很慢,尤其是条件复杂或无法使用索引时。
优化方式:
- count 查询只保留必要条件,不要 join 无关表。
- 为 count 条件建立合适索引。
- 对实时性要求不高的总数使用缓存。
- 对深分页场景不返回精确总数,只返回是否有下一页。
有些后台页面并不需要精确总数,可以使用 pageSize + 1 查询:
1 | select id, username, email, created_at |
其中 limit = pageSize + 1。如果返回数量大于 pageSize,说明还有下一页。
使用分页插件的注意点
MyBatis 常用分页插件可以减少手写 offset 和 count 的代码。但插件不是性能优化工具,它只是帮你生成分页 SQL。
使用分页插件时仍然要关注:
- 原始 SQL 是否合理。
- 是否存在不必要的 join。
- order by 是否使用索引。
- count SQL 是否过慢。
- pageSize 是否限制。
不要因为用了插件就忽略执行计划。
小结
MyBatis 分页查询的基础是 limit offset,pageSize 加 count,但真正影响线上性能的是索引、字段选择、排序方式和深分页。普通后台列表可以使用 offset 分页,并限制 pageSize;大数据量滚动加载应优先考虑游标分页;所有核心分页 SQL 都应通过 explain 检查执行计划。







