mysql 5.7 的 limit 偏移量执行机制强制读取并排序 offset + limit 行后丢弃前 offset 行,即使有索引也常因回表成本高被优化器放弃;其不支持降序索引,order by desc 易触发 using filesort,且 null 值导致排序结果不稳定。

MySQL 5.7 的 LIMIT 偏移量执行机制导致索引“被放弃”
MySQL 5.7 并不会跳过前 offset 行,而是必须读取并排序至少 offset + limit 行数据,再丢弃前 offset 行。当 offset 达到 10 万级,即使 WHERE 和 ORDER BY 字段都有索引,优化器也可能判定:全表扫描 + 文件排序,比走索引再回表更省资源——尤其当回表成本高(比如要查 TEXT、VARCHAR(2000) 等大字段)时。
ORDER BY 在 5.7 中无法利用复合索引做稳定排序
MySQL 5.7 不支持降序索引(INDEX (created_at DESC, id DESC) 会被忽略方向),只能按索引定义顺序(升序)扫描。如果查询写的是 ORDER BY created_at DESC, id DESC,哪怕有 INDEX (created_at, id),也会触发 Using filesort;而一旦进入文件排序,LIMIT 就无法提前终止排序过程,必须排完 offset + limit 行才能裁剪。
- 检查方式:
EXPLAIN SELECT ... ORDER BY ... LIMIT ...,看Extra是否含Using filesort - 错误尝试:
ORDER BY DATE(created_at) DESC或WHERE status = ? AND created_at > ? ORDER BY id—— 范围条件后跟非连续字段,索引失效 - 正确建法:用
INDEX (status, created_at, id),且查询中ORDER BY必须严格匹配前缀顺序和方向(升序优先)
延迟关联在 5.7 中需显式干预才生效
5.7 的子查询优化器对 SELECT id FROM ... ORDER BY ... LIMIT offset, size 不一定自动识别为“覆盖索引扫描”,尤其当外层 JOIN 没加提示时,可能仍走嵌套循环+回表。常见翻车点:
- 没加
STRAIGHT_JOIN,优化器自行决定驱动表,导致子查询被物化成临时表再 join,失去索引优势 - 子查询里用了
SELECT *或非主键字段,破坏覆盖索引前提 - 索引未包含
WHERE条件字段,例如只建了INDEX (created_at)却查WHERE status = 'paid',索引无法过滤,退化为全索引扫描
实操写法应强制路径:SELECT o.* FROM orders o STRAIGHT_JOIN (SELECT id FROM orders WHERE status = 'paid' ORDER BY created_at LIMIT 100000, 20) t ON o.id = t.id
NULL 值与排序不稳定性让结果不可靠
当 ORDER BY 字段存在大量 NULL(如 updated_at 允许为空),MySQL 5.7 默认把 NULL 当作最小值处理,但索引 B+ 树中 NULL 的物理位置不保证连续;加上 5.7 不支持 ORDER BY ... NULLS LAST,会导致子查询返回的 id 序列不稳定——同一页刷新两次,可能漏掉某条记录或重复出现。
真正难缠的不是性能数字,而是这种“偶发性错乱”:它不报错,不慢得明显,却让后台导出、审计对账这类强一致性场景彻底失效。











