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

LIMIT 偏移量一大,查询就慢——这不是 MySQL 有 bug,而是它必须扫描前 offset + size 行才能数出你要的那几条。延迟关联不是“黑魔法”,它是把“扫百万行再扔掉”变成“扫索引取 10 个 ID,再精确捞 10 行”的务实拆解。
延迟关联 SQL 怎么写才不白写
核心结构是 INNER JOIN 套子查询,但写错一个地方,优化就归零:
- 子查询必须只
SELECT id(或主键),不能带其他字段,否则无法走覆盖索引 - 子查询的
WHERE和ORDER BY必须能共用同一个联合索引,比如WHERE status = 1 ORDER BY created_at DESC就得建(status, created_at)索引 - 外层
JOIN必须用主键等值关联,别用IN—— MySQL 8.0 以前对IN (SELECT ... LIMIT)支持差,容易退化成全表扫描 - 示例(安全写法):
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_at, id) - 执行
EXPLAIN FORMAT=JSON,重点关注子查询部分的key是否命中预期索引、rows是否接近offset + size(理想值应远小于此)
延迟关联和游标分页怎么选
延迟关联保留“跳页”能力,但代价是每次都要跑一次大偏移子查询;游标分页性能恒定,但要求产品逻辑接受“只能下一页”:
- 后台管理类系统(支持输入页码、跳转任意页)→ 用延迟关联,但需确保索引严格覆盖
- 用户端列表(朋友圈、订单流、商品瀑布流)→ 直接用游标,例如:
SELECT * FROM `order` WHERE status = 'PAID' AND id > 123456789 ORDER BY id DESC LIMIT 20;
- 游标值必须来自上一页**最后一条真实返回的 id**,不能用 “第 N 页 × 每页条数” 算出来的理论值——删改会导致 ID 不连续,查漏或重复
真正容易被忽略的是:延迟关联的收益高度依赖索引设计精度。哪怕只漏掉一个字段(比如排序字段不在联合索引里),子查询就会退化成全表扫描+filesort,外层 JOIN 再快也救不回来。上线前务必用生产数据量级验证 EXPLAIN 结果,而不是只看开发库里“好像快了”。


















