深分页优化核心是绕开limit offset, size的物理扫描:用延迟关联(先查主键再join)或游标分页(基于上一页末位值查询),并配合联合覆盖索引,同时业务层限制翻页深度。

深分页慢不是SQL写得不对,而是MySQL必须扫描并丢弃前offset行——优化核心是绕开LIMIT offset, size的物理扫描逻辑。
用延迟关联减少回表开销
先查主键再JOIN,避免全字段扫描和大量无效回表:
- 子查询只选
id,且WHERE + ORDER BY字段要有联合索引(如(batch_id, id)或(created_at, id)),确保走覆盖索引 - 外层用
JOIN精准拉取完整行,利用主键等值匹配,走const或eq_ref,极快 - 别用
IN (SELECT ... LIMIT),MySQL 8.0之前不支持,会报错
示例:
SELECT t.* FROM t_coupon_task_fail t INNER JOIN (SELECT id FROM t_coupon_task_fail WHERE batch_id = '1830889785603571980' ORDER BY id LIMIT 4999990, 10) t1 ON t.id = t1.id;
改用游标分页彻底避开offset
适合“下一页”类场景(如无限滚动),性能稳定、不随页码衰减:
- 记住上一页最后一条记录的排序字段值(如
id或created_at),下一页直接从该值之后查起 - 排序字段必须唯一或加主键兜底,例如
ORDER BY created_at DESC, id DESC,防止重复值漏数 - 首次查第一页:
SELECT * FROM orders WHERE status = 'paid' ORDER BY id DESC LIMIT 20 - 第二页(假设最后
id = 105872):SELECT * FROM orders WHERE status = 'paid' AND id
加覆盖索引支撑高效定位
没有合适索引,以上两种方法都难生效:
- 若按
batch_id筛选+按id排序,建联合索引:ALTER TABLE t_coupon_task_fail ADD INDEX idx_batch_id_id (batch_id, id); - 若按
created_at倒序分页,建(created_at, id)索引,让子查询能完全走索引不回表 - 避免在
ORDER BY字段上使用函数或表达式,否则索引失效
业务层配合收敛翻页深度
技术优化再强,也难应对用户真翻到50万页:
- 前端限制最大可跳页码(如最多到第200页),后端校验拦截
- 对高频访问页(如前100页)做缓存预热,后台定时刷ID列表到Redis或分页映射表
- 对冷数据或审计类场景,改用导出代替翻页,或提供时间范围筛选替代无条件深翻











