MySQL深分页变慢主因是OFFSET需扫描并丢弃大量行,非配置问题;推荐游标分页(如WHERE id > 6800000)或延迟关联(先查ID再JOIN),要求ORDER BY字段有索引且避免函数操作。

MySQL深分页变慢,核心不是LIMIT本身,而是OFFSET需要先扫描并丢弃大量行。数据量越大,跳过的行越多,性能越差——这不是配置问题,是执行逻辑决定的。
用“游标分页”替代OFFSET(推荐首选)
把“跳过N行”改成“从某个确定值之后取数据”,彻底绕开偏移扫描。
- 要求排序字段有索引且可比较,比如主键id或时间戳updated_at
- id升序分页:上一页最后一条id是6800000,下一页写SELECT * FROM t WHERE id > 6800000 ORDER BY id LIMIT 20
- 时间倒序分页(如ORDER BY updated_at DESC):上一页最后一条updated_at是'2026-04-10 15:22:33',下一页写SELECT * FROM t WHERE updated_at < '2026-04-10 15:22:33' ORDER BY updated_at DESC LIMIT 20
- 若时间字段不唯一(如多条记录同秒级时间),必须叠加主键防漏/重:WHERE updated_at < ? OR (updated_at = ? AND id < ?)
延迟关联:当必须支持任意页码跳转时
先用覆盖索引快速拿到ID,再JOIN查全量,避免大偏移量下回表扫描整行数据。
- 第一步只查id(走主键/二级索引):SELECT id FROM t WHERE type = 8 ORDER BY id LIMIT 1000000, 20(毫秒级)
- 第二步用这些id关联原表:SELECT a.* FROM t a JOIN (SELECT id FROM t WHERE type = 8 ORDER BY id LIMIT 1000000, 20) b ON a.id = b.id
- 关键点:子查询中只选id;主表a.id必须有索引(主键天然满足);禁止在子查询里写SELECT *
索引与字段设计要点
再好的写法也依赖底层支撑,否则游标和延迟关联都无效。
- ORDER BY字段必须建索引,否则WHERE条件无法高效过滤,游标就退化成全表扫描
- 避免在分页字段上使用函数或表达式,如ORDER BY DATE(created_at),会破坏索引使用
- 若业务允许,可限制最大页码(如只开放前100页),后端直接拒绝offset过大的请求
- 主键尽量用自增整型,避免UUID等无序值导致B+树分裂频繁、范围查询效率下降
不建议依赖的“优化”方式
有些做法看似简化,实际隐患明显,慎用。
- WHERE id > N LIMIT 20:仅在id严格连续、无删除时成立;一旦删过记录,页码和实际位置就错位
- 加USE INDEX提示:不能解决OFFSET本身的扫描逻辑,只是引导走哪个索引,治标不治本
- 提高sort_buffer_size:对深分页几乎无改善,因为瓶颈不在内存排序,而在磁盘I/O遍历


















