深分页性能雪崩源于mysql执行limit offset,size时需扫描并丢弃前offset行;优化需数据库与java协同:推荐游标分页(keyset pagination)用于下一页场景,要求排序字段有联合唯一索引;延迟关联适用于跳页需求,子查询只查主键并join回表;java侧应加缓存、超时控制、参数校验及分页策略自动切换。

深分页性能雪崩,本质是 MySQL 在执行 LIMIT offset, size 时,必须扫描并丢弃前 offset 行——哪怕只返回 20 条数据,也要读完 100 万行再扔掉。Java 层不参与扫描,但它是请求发起方、参数组装者和结果承接者,优化必须“数据库 + Java 应用”双线协同。
游标分页(Keyset Pagination):最稳、最推荐的方案
适用于“下一页”“加载更多”类场景,彻底绕开 offset。
- 核心逻辑:记住上一页最后一条记录的排序字段组合(如
created_at, id),下一次查询用WHERE (created_at, id) 限定起点 - Java 中只需把上次响应里的
last_created_at和last_id当作参数传入下一次 DAO 查询 - 必须确保排序字段有联合唯一索引(例如
INDEX(created_at DESC, id DESC)),防止时间重复导致漏/重数据 - 注意并发写入:新增或删除可能影响边界,可在 Java 层加简单校验(如查到 19 条就补查 1 条,或限制单次查询窗口不超过 5 秒)
延迟关联(Deferred Join):兼容跳页需求的兜底方案
适合后台系统需支持“输入页码跳转”的场景(如管理员查第 862 页)。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- SQL 写法分两层:子查询只查主键(走索引),外层 JOIN 回表取全量
SELECT o.* FROM orders o INNER JOIN ( SELECT id FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 1000000, 20 ) tmp ON o.id = tmp.id;
- Java 中无需改调用方式,但必须确保子查询里只选
id(或索引覆盖字段),且ORDER BY字段已建好复合索引(如INDEX(status, created_at, id)) - 避免踩坑:子查询不能
SELECT *;JOIN 条件必须是主键等值;若业务字段太多,索引太宽反而拖慢,此时建议切回游标或加缓存
Java 层轻量级配合策略
数据库优化之外,Java 侧能显著降低抖动感知和失败风险:
- 对高频深分页接口(如 page > 500)做本地缓存或 Redis 缓存,缓存 key 包含筛选条件 + 排序参数 + offset,过期时间设为 2–5 分钟
- MyBatis 或 JdbcTemplate 设置
queryTimeout=3000(3 秒),避免慢查询卡死线程池 - 前端传参校验:Java 接口层拦截
page × size > 100_000的请求,直接返回提示“请改用搜索或切换为滚动加载”,或自动降级为游标模式 - 封装分页工具类:根据
pageNum自动判断浅分页(直接LIMIT)、中分页(延迟关联)、深分页(先查 last_id 再回表),对业务代码透明
索引与数据结构配合要点
没有索引,所有优化都失效:
- 排序字段必须有索引,且顺序要匹配
ORDER BY(如ORDER BY status, created_at DESC→ 索引INDEX(status, created_at)) - 主键尽量用自增整型,避免 UUID 等随机值破坏聚簇索引局部性
- 若业务允许,可增加
version或seq_no字段辅助游标,提升并发安全性
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










