延迟关联能减少回表次数,是因为它将分页的“排序跳行”与“取全量数据”分离:子查询仅通过覆盖索引获取目标主键(不回表),外层用这些主键精确回表size次,将原O(offset+size)次回表降至O(size)次。

为什么延迟关联能减少回表次数
普通 LIMIT offset, size 查询在大偏移量下会先扫描并丢弃前 offset 行,每行都可能触发一次回表(尤其是 SELECT * 或非索引字段)。而延迟关联把“排序+跳行”和“取全量数据”拆成两步:子查询只查主键(或覆盖索引字段),走索引即可完成;外层用这些主键精确匹配,只回表 size 次。本质是把 O(N) 回表压降到 O(1) × size。
延迟关联的正确写法与索引要求
必须确保子查询能走索引,否则延迟关联反而更慢。常见错误是子查询里 ORDER BY 字段没索引,或 WHERE 条件无法命中联合索引最左前缀。
- 子查询只选主键(如
id),且WHERE + ORDER BY必须能共用一个联合索引,例如(status, created_at)支持WHERE status = 1 ORDER BY created_at DESC - 外层
JOIN的连接字段必须是主键或唯一索引,否则ON o.id = tmp.id可能走全表扫描 - 避免在子查询中使用
SELECT *、函数、DISTINCT或GROUP BY,这些会让优化器放弃覆盖索引
示例:
SELECT o.* FROM orders o INNER JOIN ( SELECT id FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 1000000, 20 ) AS tmp ON o.id = tmp.id;
延迟关联 vs 基于ID的范围查询,怎么选
延迟关联适合需要随机跳页(比如后台导出第500页)、且排序字段不是主键的场景;而基于 id > ? 的范围查询更轻量,但要求主键单调递增、不能有大量删除,且不支持跳页。
- 用延迟关联:当业务允许输入任意页码(如“跳转到第 N 页”),且已有合适联合索引支撑排序
- 用
WHERE id > ?:当是自增主键、分页方向固定(如按id ASC向下翻)、且前端只提供“下一页”按钮 - 混合策略更实用:前100页用传统
LIMIT,超过后自动切到延迟关联或游标模式
容易被忽略的三个细节
延迟关联不是加个 JOIN 就万事大吉。实际部署时这几个点常被跳过:
- 子查询结果集若为空(比如
LIMIT 1000000, 20超出总行数),外层JOIN会返回空结果——需额外判断是否到底部 - 如果原表没有主键,或
id不是主键,子查询返回的字段必须是唯一 + 有索引,否则外层JOIN性能崩塌 -
EXPLAIN看子查询的type必须是range或index,且Extra里不能出现Using filesort或Using temporary



















