limit 10000,20 变慢是因为mysql需扫描并暂存前10000行,若order by无索引还会触发filesort和临时表,导致rows_examined暴增;推荐游标分页替代。

为什么 LIMIT 10000, 20 会变慢?
MySQL 在执行深分页时,并不是跳过前 10000 行再取 20 行,而是先扫描并暂存前 10000 行(即使不返回),再取后续 20 行。如果 ORDER BY 字段无索引或索引未覆盖查询列,还会触发 filesort 和临时表,导致 rows_examined 暴涨。
典型表现:EXPLAIN 显示 rows 很大,Extra 中出现 Using filesort 或 Using temporary;慢日志里 Query_time 和 Rows_examined 明显偏高。
用游标分页替代 LIMIT offset, size
核心思路是:不依赖行号偏移,而用上一页最后一条记录的排序键值作为下一页起点。前提是排序字段唯一且有索引(推荐用主键或带唯一约束的业务字段)。
- 原写法(低效):
SELECT * FROM orders ORDER BY created_at DESC LIMIT 10000, 20 - 改写为(高效):
SELECT * FROM orders WHERE created_at ,其中 <code>'2025-08-15 10:23:44'是上一页最后一条的created_at - 必须确保
created_at加了索引,且查询条件能命中索引最左前缀;若存在相同时间戳,需追加主键去重,如WHERE (created_at, id)
强制走覆盖索引减少回表
当查询列远多于 ORDER BY 字段时,MySQL 可能放弃索引排序,转而全表扫描+filesort。覆盖索引可避免回表,同时让排序在索引内完成。
- 检查执行计划:若
key显示用了索引但Extra仍有Using where; Using filesort,说明索引未覆盖SELECT列或WHERE条件 - 创建联合索引示例:
CREATE INDEX idx_created_id ON orders(created_at DESC, id DESC),适用于SELECT id, created_at FROM orders ORDER BY created_at DESC LIMIT 20 - 避免
SELECT *,只查真正需要的字段;否则即使有索引,也可能因回表开销大而退化
物理分页 + 应用层缓存组合策略
对「用户只看前几页、极少翻到万级偏移」的场景,纯 SQL 优化收益有限,应配合应用层控制。
- 后端限制最大
offset(如 > 5000 就拒绝或降级为搜索) - 对高频访问的「热门页码」(如第 1、10、20 页),用 Redis 缓存结果集 ID 列表,再异步查详情
- 导出类需求不要走分页接口,改用
WHERE id BETWEEN ? AND ?分段拉取,配合自增主键范围更稳定
深分页真正的瓶颈不在语法,而在 MySQL 的 B+ 树索引无法跳过中间节点直接定位偏移位置。游标分页是目前最可靠解法,但要求业务能接受「不能跳页」「最后一页可能不准」等约束——这点容易被忽略,上线前务必和产品对齐。











