limit offset大时变慢是因为mysql需逐行扫描前offset+size行再丢弃前offset行,导致线性i/o与cpu开销;索引无法跳过数据,仅能加速排序而非跳转,配合函数、方向不匹配或顺序错误等写法则进一步加剧失效。

为什么LIMIT offset大时索引扫描反而变慢
因为MySQL不会“跳”到offset位置,而是老老实实从排序结果开头逐行扫描,直到凑够offset + size行——哪怕你只想要10条,它也得把前10万行全扫一遍再丢弃。索引再好,也救不了这种线性扫描逻辑。
LIMIT导致索引失效的典型场景
不是索引没建,而是查询写法让优化器没法用索引跳过数据。常见触发点:
-
ORDER BY created_at DESC但索引是(created_at ASC),方向不匹配 → 强制Using filesort -
WHERE DATE(created_at) = '2024-01-01'对字段用函数 → 索引无法下推,全表扫描 - 联合索引顺序错,比如查
WHERE status = 1 ORDER BY created_at,但索引是(id, created_at, status)→ 最左前缀不命中 -
SELECT *配合二级索引排序 → 每行都要回表,随机IO爆炸
延迟关联(Deferred Join)怎么避免无效索引扫描
核心是把“找ID”和“取数据”拆开,让第一步只走轻量索引,不碰数据页:
- 子查询必须只
SELECT id,且ORDER BY字段有索引(最好是主键或覆盖索引最左列) -
WHERE条件必须下推到子查询里,否则外层JOIN会放大中间结果集 - 外层必须用
INNER JOIN,别用IN (subquery)——MySQL 8.0.22之前不支持带LIMIT的IN子查询 - 示例:
SELECT o.* FROM orders o INNER JOIN ( SELECT id FROM orders WHERE status = 1 ORDER BY id DESC LIMIT 1000000, 20 ) AS tmp ON o.id = tmp.id;
游标分页为什么能绕过索引扫描瓶颈
它彻底不用OFFSET,改用排序字段值做条件,B+树直接定位起点,后续只读size条:
- 必须保证排序字段单调唯一,比如
id或created_at + id组合 -
WHERE id > ? ORDER BY id LIMIT 20是范围扫描,EXPLAIN里rows≈20;而LIMIT 1000000, 20是全索引扫描,rows≈1000020 - 客户端必须传上一页末尾的
id,不能靠页码计算;混用ASC/DESC会漏数据 - 新数据插入可能导致“漂移”,比如按时间排序时,刚发布的记录插在旧页面中间,造成重复或跳过
真正卡住性能的,从来不是索引有没有,而是你默认MySQL能“跳过”数据——而它从不跳。











