INNER JOIN延迟关联能绕过LIMIT扫描浪费,是因为它将“全字段扫描+丢弃”拆为两步:先用覆盖索引快速查出目标主键,再精准回表10次,避免了逐行扫描并丢弃前N行的I/O和内存开销。

为什么 INNER JOIN 延迟关联能绕过 LIMIT 的扫描浪费
MySQL 执行 LIMIT 1000000, 10 时,必须从排序结果开头逐行扫描、计数、丢弃前 1000000 行——哪怕只用最后 10 行。问题核心不是“排序慢”,而是“白读白丢”。延迟关联的本质是把“全字段扫描 + 丢弃”拆成两步:先用覆盖索引快速捞出需要的主键(或唯一标识),再用这些 ID 精准回表。这样避免了大量无效 I/O 和内存搬运。
怎么写 INNER JOIN 延迟关联语句(带 ORDER BY 场景)
假设表 orders 有主键 id,你要查按 created_at 倒序的第 100 万页(每页 10 条):
SELECT o.*
FROM orders o
INNER JOIN (
SELECT id
FROM orders
ORDER BY created_at DESC
LIMIT 1000000, 10
) tmp ON o.id = tmp.id
ORDER BY o.created_at DESC;
关键点:
- 子查询只查
id,走created_at索引就能完成(覆盖索引),不回表 - 外层
JOIN用主键等值匹配,走聚簇索引,回表仅 10 次 - 外层
ORDER BY不可省——因为子查询里的排序不保证外层输出顺序(尤其 MySQL 8.0+ 优化器可能重排) - 如果
created_at有重复值,需加id作为二级排序条件,否则分页结果不稳定
和 WHERE id > last_id 方案比,什么时候该选 JOIN
延迟关联不是万能替代品,它更适合以下场景:
- 业务无法维护“上一页最后 ID”(比如用户跳页、刷新、后退)
- 排序字段不是主键且不唯一(如
status, updated_at联合排序) - 需要严格按原始分页逻辑(第 N 页)而非游标式分页(下一页)
- 表有复合排序需求,但主键无法直接用于范围过滤(例如按用户分组内时间倒序)
而 WHERE id > last_id 更轻量、更快,但要求客户端配合保存状态,且只适用于单字段单调递增/递减的排序依据。
容易踩的坑:JOIN 后没加 ORDER BY 或索引没建对
常见错误现象:JOIN 结果顺序错乱、性能没提升甚至更差。原因往往在:
- 子查询里用了
ORDER BY created_at DESC,但外层没再ORDER BY,MySQL 返回顺序不可靠 -
created_at字段没索引,或索引不是最左前缀(例如索引是(user_id, created_at),但查询没带user_id条件) - 子查询返回的
id集合过大(比如LIMIT 5000000, 100),导致JOIN时临时表膨胀 - 误写成
LEFT JOIN或漏掉ON条件,引发笛卡尔积
真正起效的前提,是子查询能用索引快速定位到那几条 id——否则只是把“白读”从主表挪到了子查询里。











