延迟关联通过子查询只查主键走覆盖索引、外层JOIN主键回表,将扫描行数从offset+size降至size,大幅提升深分页性能,但依赖精准联合索引设计。

延迟关联能显著缓解超大偏移量分页的性能断崖,但它不是套个 INNER JOIN 就自动生效——子查询是否走覆盖索引、排序字段是否被索引真正覆盖、外层关联是否用主键,三者缺一不可。
延迟关联 SQL 必须写成两层结构
核心是把“取 ID”和“查整行”拆开,让 MySQL 分别走索引和主键聚簇索引。写错一层,优化就归零:
- 子查询必须只
SELECT id(或唯一主键),不能带其他字段,否则无法走覆盖索引 - 子查询的
ORDER BY字段必须在联合索引最左前缀上;比如按create_time DESC排序,索引就得是(status, create_time),而不是(create_time, status) - 外层
JOIN必须用主键等值关联,禁止用IN (SELECT ...)—— MySQL 8.0 以前对这种写法优化差,容易退化成全表扫描 - 示例(安全写法):
SELECT o.* FROM `order` o INNER JOIN ( SELECT id FROM `order` WHERE status = 'PAID' AND create_time >= '2025-01-01' ORDER BY create_time DESC LIMIT 1000000, 20 ) tmp ON o.id = tmp.id;
为什么加了索引还是慢?重点看执行计划
很多人建了 (type, id) 索引,EXPLAIN 却显示 Using filesort 或 rows 高达几十万——说明索引没被真正复用:
- 检查子查询里
ORDER BY字段是否在索引最左前缀上;如果排序的是updated_at,但索引是(type, id),那就完全失效 - 如果
ORDER BY字段有大量重复值(比如created_at精确到秒),MySQL 可能无法稳定利用索引顺序,此时索引应补上主键:例如(status, created_time, id) - 执行
EXPLAIN FORMAT=JSON,重点关注子查询部分的key是否命中预期索引、rows是否接近offset + size(理想值应远小于此)
延迟关联 vs 游标分页:选哪个取决于业务约束
延迟关联保留跳页能力,但每次都要跑一次大偏移子查询;游标分页性能恒定,但要求产品逻辑接受“只能下一页”:
- 后台管理类系统(支持输入页码、跳转任意页)→ 用延迟关联,但需确保索引严格覆盖
- APP 列表页、日志流式浏览 → 优先用游标分页,例如
WHERE id > ? ORDER BY id LIMIT 10,性能几乎与偏移量无关 - 非自增主键场景(如时间戳排序),游标需补主键防重复:
WHERE create_time
最容易被忽略的一点:延迟关联只解决“扫描行数多”的问题,不解决“排序本身重”的问题。如果 WHERE 条件筛选后仍有百万级结果集再排序,即使用了延迟关联,子查询里的 ORDER BY 仍可能触发临时表或文件排序——这时候得先缩小结果集,再考虑延迟关联。


















