MyBatis高效分页需避开COUNT(*)统计和OFFSET深度扫描两大陷阱:禁用count、改用游标分页(基于主键或时间字段)、精简SQL与关联查询、建立覆盖索引,并配合数据库层优化。

MyBatis 本身不提供开箱即用的高性能分页能力,面对百万级以上数据,直接用 LIMIT offset, size 会严重拖慢查询——因为数据库必须扫描并跳过前 offset 行。要真正高效翻页,关键不是“怎么配 PageHelper”,而是“怎么避开传统分页的两大陷阱”:COUNT(*) 统计耗时 + OFFSET 深度扫描。
禁用 COUNT 或改用异步/缓存策略
多数列表页(如信息流、“加载更多”)根本不需要精确总页数。强行查总数,等于每次请求都多执行一次全表或索引扫描。
- PageHelper:调用
PageHelper.startPage(pageNum, pageSize, false),第三个参数false明确跳过 count 查询 - MyBatis-Plus:设置
page.setSearchCount(false),再传入selectPage - 若必须显示总页数,可用定时任务统计表行数快照(如每5分钟更新一次
SELECT COUNT(*) FROM table结果到 Redis),前端展示近似值即可
放弃页码,改用游标分页(推荐深分页场景)
游标分页不依赖页码,而是以“上一页最后一条记录的主键值”为锚点,彻底绕开 OFFSET。它性能稳定、可水平扩展,适合高并发和大数据量。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 要求:主键(如
id)或时间字段(如create_time)有唯一性且已建索引 - SQL 示例:
SELECT * FROM user WHERE id > #{lastId} ORDER BY id LIMIT 20 - MyBatis-Plus 写法:
lambdaQuery().gt(User::getId, lastId).orderByAsc(User::getId).last("LIMIT " + size).list() - 前端需保存并传递
lastId,不支持随机跳页,但换页响应极快
精简查询本身:避免关联爆炸与字段冗余
分页慢常因 SQL 写法不当放大性能问题,而非分页插件本身。
立即学习“Java免费学习笔记(深入)”;
- 禁用
<collection>做一对多嵌套分页——易触发 N+1 或笛卡尔积,应拆成两阶段:先查主表 ID 列表,再用IN批量查关联数据(注意控制 IN 数量,建议 ≤ 1000) - 只查必要字段,不用
SELECT *;XML 中用<resultMap>精确映射,减少对象封装和网络传输 - 大文本、JSON、BLOB 字段延迟加载,或单独接口获取
配套数据库层优化不能少
再好的分页逻辑,没有底层支撑也白搭。
- 覆盖索引:确保
WHERE条件字段、排序字段、游标字段(如id)落在同一联合索引中,避免回表 - 流式导出大数据:用
ResultHandler或 MyBatis-Plus 的selectObjs配合fetchSize = Integer.MIN_VALUE,逐批拉取防 OOM - 防恶意请求:PageHelper 开启
reasonable=true自动修正越界页码;MyBatis-Plus 设置最大页码限制,拦截pageNum=999999类攻击

















