延迟关联通过先查主键再join回表来优化深分页,避免全量扫描与丢弃;其前提是排序字段有覆盖索引(如(created_at,id)),且子查询仅选主键或索引列,外层join后需显式order by保证顺序。

延迟关联不是用来“优化分页索引”的,而是用已有索引(尤其是覆盖索引)绕过 LIMIT offset, size 的扫描瓶颈。它不创建新索引,但对索引有强依赖——没对的索引,延迟关联也救不了。
为什么直接用 LIMIT 100000, 20 会变慢
MySQL 执行深分页时,并不是跳到第 100000 行再取 20 行。它实际要:沿排序字段的索引逐条读取主键 → 拿每个主键回表查完整行 → 在内存里攒够 100020 条后,丢掉前 100000 条。IO 和 CPU 都浪费在被丢弃的数据上。
尤其当排序字段是二级索引(比如 created_at)时,每读一个主键就要回一次聚簇索引,百万级偏移下就是百万次随机 IO。
INNER JOIN 延迟关联写法必须满足的条件
下面这个结构看着简单,但漏掉任一条件,性能可能比原写法还差:
- 子查询中
ORDER BY的字段必须有索引,且该索引最好包含主键(例如(created_at, id)),否则无法保证排序稳定,也难走覆盖索引 - 子查询只能选主键(如
id)或索引中已包含的列;一旦出现SELECT id, status而status不在索引里,就会强制回表,失去延迟意义 - 外层
JOIN后不能加WHERE过滤非索引字段(比如AND title LIKE '%abc%'),否则优化器大概率放弃使用该执行计划 - 外层必须显式写
ORDER BY,MySQL 不保证JOIN后结果顺序和子查询一致
延迟关联 vs 游标分页:什么时候该换方案
延迟关联适合「需要跳转任意页码」的后台管理类场景(比如运营查第 5000 页订单)。但它仍是基于偏移量的,只是把扫描压缩到了主键层面。
而游标分页(WHERE created_at )彻底去掉 <code>OFFSET,性能恒定,但要求业务接受「只能顺序翻页」和「不能跳页」。
两者不互斥:你可以用延迟关联支撑「跳页」入口,同时用游标分页承载「下拉加载」主流程。
最容易被忽略的一点:延迟关联带来的性能提升,几乎全部来自索引设计是否合理。没有 (sort_col, id) 这类联合索引,SELECT id FROM ... ORDER BY sort_col 本身就会全表扫描——后面套再多层 JOIN 也没用。











