MySQL执行LIMIT 10000,10需扫描并丢弃前10000行,仅返回后10行;有效优化方式为游标分页(WHERE id > last_id)或延迟关联(先查ID再JOIN),避免offset带来的线性性能损耗。

因为 MySQL 不会跳过前 10000 行,而是老老实实扫描、排序、加载并丢弃它们——LIMIT 10000, 10 实际要处理 10010 行,其中 10000 行纯属白干。
MySQL 执行 LIMIT offset, limit 的真实过程
你写的是“取第 10001 到 10010 条”,但 MySQL 没有“直接定位到第 N 行”的能力。它必须从索引起点开始逐条读取,边读边计数:
- 从主键或排序列的索引叶子节点第一条开始遍历
- 每读一条就 +1 计数,直到累计读够
offset + limit条(这里是 10010 条) - 把前
offset条(10000 条)在内存里扔掉 - 把剩下的
limit条(10 条)返回给客户端
关键点:offset 行不是“跳过”,是“扫描+加载+丢弃”。哪怕只用 id 字段,只要 SELECT * 或字段没被覆盖索引包含,就会触发回表;而回表次数 = 扫描行数,不是返回行数。
为什么加了索引还是慢?
索引能加速查找和排序,但解决不了“扫描 10000 行再扔掉”这个动作本身。常见误区:
-
ORDER BY id有主键索引?没错,但 MySQL 仍需顺着 B+ 树叶子节点链表走 10010 步 - 加了
WHERE status = 'paid'并建了idx_status?如果该索引不包含id或排序字段,MySQL 可能先查出全部匹配 ID(比如 50 万条),再排序取第 10000 个位置,开销更大 - 用了
SELECT id, name而非*?若idx_id_name是覆盖索引,这步能避免回表,但offset扫描量不变
也就是说:索引治标,不治本。它让“每一步”更快,但“走一万步”这件事本身没变。
哪些优化方式真有效?
真正起效的不是调参或换索引,而是绕开 OFFSET 这个机制:
-
游标分页(推荐):记住上一页最后一条的
id(或create_time, id组合),下一页查WHERE id > 123456 ORDER BY id LIMIT 10。性能恒定,不受页码影响 -
延迟关联:先用覆盖索引子查询拿到目标页的 10 个
id,再JOIN主表取全量数据。例如:SELECT t1.* FROM orders t1 INNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 10000, 10) t2 ON t1.id = t2.id
这样最多回表 10 次,而非 10010 次 -
业务限制:前端禁止输入大页码(如 > 200),改用“加载更多”按钮;或后端对
offset > 10000的请求直接拒绝并提示
注意:WHERE id BETWEEN ? AND ? 看似简洁,但依赖 ID 严格连续,现实中几乎不可用——删过数据、分布式 ID、事务回滚都会导致空洞。
最常被忽略的一点:游标分页要求排序字段组合必须唯一且稳定。如果仅用 create_time 排序,同一秒内多条记录会导致漏数据或重复;必须补上 id 做二级排序,并在查询条件中一并判断。


















