子查询优化的核心是分两步:先用索引快速定位起始id,再精准获取全量数据;标准写法为子查询只查主键并order by索引字段,外层join原表;进阶写法用子查询获起始id后范围过滤。

子查询优化的核心思路
直接写 LIMIT 1000000, 10 会让 MySQL 扫描并跳过前 1000000 行,再取 10 行——即使有索引,也要处理 1000010 条记录,其中大量回表和计数是性能瓶颈。子查询优化的关键是:把“找起始位置”和“取完整数据”拆成两步,让第一步只走索引(甚至覆盖索引),极快定位目标 ID,第二步再精准查全量。
标准写法:先查 ID,再 JOIN 取全字段
这是最常用、兼容性好、效果显著的写法,适用于支持跳页的场景(比如后台列表页):
- 子查询只 SELECT 主键(如 id),ORDER BY 字段必须有索引(最好是主键或联合索引最左前缀)
- 外层用 INNER JOIN 关联原表,避免使用 IN(尤其在 MySQL 8.0 以前不支持 IN 子句带 LIMIT)
- WHERE 条件必须下推到子查询里,否则外层 JOIN 会放大结果集
示例:
INNER JOIN (
SELECT id FROM orders WHERE status = 1 ORDER BY id LIMIT 1000000, 10
) b ON a.id = b.id;
进阶写法:用子查询获取起始 ID 再范围过滤
适合排序字段和查询条件较固定、且主键有序的场景,能进一步减少子查询扫描量:
- 子查询只查一个值:SELECT id FROM orders ORDER BY id LIMIT 1000000, 1
- 外层用 WHERE id >= (子查询) + LIMIT 10,利用主键索引快速定位
- 注意:若存在并发插入或删除,需配合 ORDER BY id ASC 确保顺序一致
示例:
SELECT * FROM ordersWHERE id >= (
SELECT id FROM orders ORDER BY id LIMIT 1000000, 1
)
ORDER BY id LIMIT 10;
实操注意事项
不光是改 SQL,这几处细节决定优化是否真正生效:
- 索引必须到位:ORDER BY 字段(如 id)要有主键或高效索引;如果加了 WHERE 条件(如 update_time > '2025-01-01'),建议建联合索引 INDEX (update_time, id)
- 避免 SELECT * 在子查询中——只查 id 是为了走覆盖索引,一旦选其他字段,可能触发回表或全表扫描
- 测试时看执行计划:EXPLAIN 中子查询的 rows 应明显小于原语句,且 Extra 出现 Using index,没有 Using filesort 或 Using temporary
- 数据分布要留意:如果 WHERE 条件过滤后实际只剩几万行,那 LIMIT 1000000, 10 本身已无意义,应提前拦截或提示“已到底”











