limit 1000000, 20慢是因为mysql必须逐行扫描并丢弃前1000000行,无法跳过;优化需用延迟关联(子查询只查id再join)或游标分页(where id
因为MySQL必须扫描并丢弃前offset行,不是跳过索引位置,而是逐行计数、过滤、排序后扔掉——哪怕你只想要20条。
为什么
LIMIT 1000000, 20要扫1000020行MySQL没有“直接定位第N行”的能力。它执行时:先按
ORDER BY生成完整逻辑结果集(可能触发Using filesort),再从头开始数,跳过前1000000行,取接下来20行。这过程无法用索引跳过,只能硬扫。
- 如果
ORDER BY字段没索引,必然全表扫描 + 磁盘排序- 即使有索引,若查询字段不在索引里(比如
SELECT *),每跳过一行都可能引发一次回表(随机IO)EXPLAIN里rows值极大,Extra出现Using temporary或Using filesort就是典型信号延迟关联(
INNER JOIN子查询)怎么写才不翻车核心是把“找id”和“取数据”拆开,但细节决定成败:
- 子查询必须只
SELECT id,且ORDER BY字段+id得落在同一个覆盖索引里,例如INDEX(status, created_at DESC, id)ON t1.id = t2.id必须是等值连接;换成IN或范围条件,MySQL 5.7+ 很可能退化为全表扫描- 不能在子查询里写
SELECT *或非索引字段,否则优化器放弃覆盖索引- 示例正确写法:
SELECT t1.* FROM orders t1 INNER JOIN ( SELECT id FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 1000000, 20 ) t2 ON t1.id = t2.id;游标分页
WHERE id 为什么更稳但限制多它用上一页末尾的
id值做边界,直接走主键索引定位,复杂度稳定在O(log n):
- 必须依赖唯一、单调的排序字段(如自增
id或created_at),且该字段已建索引- 排序字段不唯一时(比如多个记录
created_at相同),得组合(created_at, id)当游标,否则漏/重- 无法跳转任意页码,只适合“下一页”场景;中间删记录或并发插入会导致数据错位
- 典型写法:
SELECT * FROM orders WHERE id复合索引顺序为什么影响这么大
索引字段顺序决定它能否支撑覆盖扫描和排序:
- WHERE条件字段放最左,比如
WHERE status = 1→ 索引第一列必须是status- ORDER BY字段紧随其后,且方向一致(
DESC要显式声明)- 最后放
id(或其他主键),确保子查询SELECT id能完全走索引,不回表- 错误示例:
INDEX(created_at, status, id)——WHERE status = 1无法用上这个索引真正卡住的从来不是SQL写法本身,而是索引是否匹配查询路径、排序字段是否唯一、以及业务是否允许放弃“跳页”换性能——这三个点漏掉任何一个,优化都会打折扣。












