limit 1000000, n 变慢是因为 mysql 必须逐行扫描并丢弃前 100 万行,时间复杂度 o(offset+size),无法跳过;优化应改用游标分页(如 where id > last_id order by id limit n),而非依赖索引或配置调整。

LIMIT 1000000, N 查询变慢,不是配置或服务器问题,而是 MySQL 必须扫描并丢弃前 100 万行——它根本不会“跳”过去。
MySQL 执行 LIMIT offset, size 的真实流程
它不是定位到第 offset + 1 行再取数据,而是:从头开始逐行读、逐行计数、直到累计读满 offset + size 行,再把前 offset 行全扔掉。
这意味着:
-
LIMIT 1000000, 10实际要扫描 1000010 行,哪怕ORDER BY id走的是主键索引 - 如果
SELECT *或非覆盖字段,每丢弃一行都可能触发一次回表(随机 I/O) - 若排序字段没索引,还会触发
Using filesort,内存不够就写磁盘临时文件 - 偏移量每增加 10 万,耗时基本线性上涨,不是“慢一点”,是“断崖式变慢”
为什么加了索引也救不了
索引只加速“找”,不解决“丢”的开销。常见误区是以为建了 idx_update_time 就够了,但实际执行中:
- 先用二级索引扫出满足
WHERE update_time > ?的 1000010 个id - 再拿这 1000010 个
id回主键索引查完整行(100 万次随机 I/O) - 最后才排序、丢弃、返回
- EXPLAIN 中
rows值极大 +Extra出现Using temporary或Using filesort就是典型信号
子查询优化必须满足的硬条件
用 SELECT t.* FROM t INNER JOIN (SELECT id FROM t ORDER BY id LIMIT 1000000, 10) tmp ON t.id = tmp.id 有效,前提是:
- 子查询里
ORDER BY字段必须有索引(主键最优,否则仍要 filesort) - 子查询只能
SELECT id(或覆盖索引包含的字段),不能SELECT *,否则失去索引扫描优势 - 外层必须用
JOIN,不能改写成IN—— MySQL 8.0 以前不支持IN子句带LIMIT - 所有过滤条件(如
WHERE update_time > '2025-01-01')必须下推到子查询里,否则外层会放大结果集
游标分页 WHERE id > ? 为什么快得多
它彻底绕开了 offset 计数逻辑,变成纯范围扫描:
- 首次查:
SELECT * FROM orders ORDER BY id LIMIT 20 - 拿到最后一条
id = 1005,下一页就是:SELECT * FROM orders WHERE id > 1005 ORDER BY id LIMIT 20 - MySQL 直接在主键 B+ 树叶子节点链表上往后走 20 条,复杂度稳定在
O(log n) - 缺陷也很实在:不能跳页(比如直接点“第 88 页”),且排序字段必须严格唯一;若用
update_time排序,得写成WHERE (update_time, id) > (?, ?)
真正卡住人的,往往不是选哪种方案,而是继续用 LIMIT offset, size 并期待“加个索引就好”——它在百万级数据上,本身就是不可扩展的设计。











