MySQL深分页变慢本质是LIMIT offset,size需扫描丢弃前offset行;优化核心是避免跳过大量数据,常用延迟关联(子查询+JOIN)和游标分页,辅以Java层缓存、超时控制与前端限制。

Java 项目里遇到 MySQL 深分页变慢,本质不是 Java 的问题,而是 SQL 执行逻辑导致的——LIMIT offset, size 在 offset 很大时,MySQL 必须扫描并丢弃前 offset 行,哪怕只返回几条数据。这种抖动往往出现在翻到第几百页、上万页后,响应时间从几十毫秒飙升到数秒甚至超时。优化核心就一条:避免让数据库“跳过大量数据”。
用延迟关联(子查询 + JOIN)减少回表和扫描
这是最常用、兼容性最强的优化方式,适合仍需支持“跳转任意页码”的业务场景(比如后台管理系统的页码输入框)。
- 先建好覆盖索引:比如按
status = ? AND created_at DESC排序分页,索引应为INDEX(status, created_at, id)—— 字段顺序不能错,id 放最后才能让子查询只走索引不回表 - SQL 写成两层:子查询只查
id,外层用主键精确关联取全字段
示例:SELECT t1.* FROM orders t1 INNER JOIN (SELECT id FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 1000000, 20) t2 ON t1.id = t2.id; - 注意别踩坑:子查询里不能写
SELECT *或非索引字段;JOIN 条件必须是ON t1.id = t2.id这种等值连接;如果业务字段太多,索引太宽反而拖慢定位,此时要考虑其他方案
改用游标分页(Keyset Pagination)彻底规避 offset
这是性能最稳、扩展性最好的方案,适合列表流式加载(如“下一页”按钮、无限滚动),但不支持直接跳转中间页码。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 核心是记住上一页最后一条记录的排序字段值,作为下一页的查询边界
示例(按自增 id 降序):SELECT * FROM orders WHERE id < 987654321 ORDER BY id DESC LIMIT 20; - 排序字段必须唯一且有索引,推荐组合
(created_at, id)防止时间戳重复;删除或并发插入可能导致漏数据或重复,可通过应用层加版本号或时间窗口校验缓解 - Java 中只需把上一页末尾的
id(或created_at + id)作为参数传入下一次查询,无需计算 offset
配合 Java 层做轻量兜底与体验优化
数据库优化之外,Java 应用侧也能降低抖动感知和失败风险。
立即学习“Java免费学习笔记(深入)”;
- 对深度分页请求加缓存:比如把第 1000 页的结果缓存 5 分钟,避免重复执行慢查询
- 设置合理超时:MyBatis 或 JdbcTemplate 配置 queryTimeout,防止一个慢查询拖垮整个线程池
- 前端限制可翻页范围:比如只允许翻到前 200 页,后面用搜索或筛选替代;或当 page × size > 100 万时自动切到游标模式
- 监控慢日志:在 Java 中打点记录 offset > 10 万的查询耗时,及时发现未优化的分页接口


















