大数据分页慢的根源是count(*)耗时和深分页offset扫描无效数据,优化需禁用/异步count、改用游标分页、精简关联查询、覆盖索引及流式处理等策略。

大数据量下分页查询慢,核心问题就两个:一是 COUNT(*) 统计总条数太耗时,二是 LIMIT offset, size 在深分页时扫描大量无效数据。MyBatis 本身不带分页能力,实际优化需结合插件(如 PageHelper)或升级为 MyBatis-Plus,并配合数据库层策略。
禁用或异步化 COUNT 查询
很多场景根本不需要精确总页数——比如“加载更多”、“无限滚动”。这时可跳过 COUNT:
- PageHelper:调用
PageHelper.startPage(pageNum, pageSize, false),第三个参数false表示不执行 count - MyBatis-Plus:用
page.setSearchCount(false),再传入selectPage - 若必须显示总页数,可改为异步统计或缓存近似值(如定时任务更新表行数快照)
改用游标分页(推荐用于高并发/深分页)
放弃页码概念,改用“上次最后 ID”作为分页锚点,彻底规避 OFFSET 扫描:
- 要求:主键或唯一有序字段(如
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,不支持随机跳页,但性能稳定、可扩展
关联查询与结果集精简
分页慢常因关联爆炸或字段冗余导致:
- 避免在分页主查中用
<collection></collection>做一对多嵌套——它会触发 N+1 查询或生成巨大笛卡尔积 - 改用两阶段查询:先分页查主表 ID 列表,再用
IN批量查关联数据(注意 IN 数量限制) - 只查必要字段,不用
SELECT *;XML 中用<resultmap></resultmap>精确映射,减少对象封装开销和网络传输 - 对大文本、JSON、BLOB 字段,考虑延迟加载或单独接口获取
其他关键优化点
这些细节容易被忽略,但影响显著:
- 索引覆盖:确保排序字段、WHERE 条件字段、分页锚点字段都在联合索引中,让查询走索引不回表
-
流式查询处理超大数据导出:用
ResultHandler或 MyBatis-Plus 的selectObjs配合fetchSize = Integer.MIN_VALUE,逐批拉取,防 OOM -
分页插件配置合理化:PageHelper 开启
reasonable=true防越界,MyBatis-Plus 设置maxLimit防恶意大页请求 - 读写分离+分库分表后分页:单库分页失效时,需中间件(如 ShardingSphere)支持归并分页,或业务层聚合











