延迟关联能减少回表开销,因其将分页拆为两步:子查询仅通过覆盖索引获取主键(避免回表),外层再用这20个id精准回表查全量数据,大幅降低随机IO次数。

为什么延迟关联能减少回表开销
MySQL 执行 LIMIT 100000, 20 时,不是“跳过前 10 万行”,而是必须先读取并排序至少 100020 行,再丢弃前 10 万——如果 SELECT * 且排序字段无覆盖索引,每行都要回表查完整数据,IO 次数直接飙到 100020 次随机读。延迟关联把这一步拆成两段:子查询只走索引捞 id(最多 20 次回表),外层再用这 20 个 id 精准查详情。
延迟关联的正确写法和索引要求
必须保证子查询能走覆盖索引,否则优化无效。常见错误是只建了 INDEX (created_at),但子查询里 ORDER BY created_at + LIMIT offset, size 仍要排序临时表。
- 建复合索引时,需包含
WHERE条件字段、ORDER BY字段、主键id(按顺序):例如ALTER TABLE orders ADD INDEX idx_status_ctime_id (status, created_at, id); - 子查询必须明确使用该索引字段:例如
SELECT id FROM orders WHERE status = 'shipped' ORDER BY created_at DESC, id DESC LIMIT 100000, 20 - 外层 JOIN 要用主键等值连接:例如
SELECT o.* FROM orders o INNER JOIN (...) t ON o.id = t.id - MySQL 5.7 及更早版本可能不自动优化子查询,可加
STRAIGHT_JOIN强制执行顺序
延迟关联在哪些场景会失效
它不是银弹,几个典型翻车点:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
ORDER BY字段有NULL值且未在索引中处理:索引可能无法按预期排序,导致子查询结果错乱 - 子查询中用了函数或表达式(如
ORDER BY DATE(created_at)):索引失效,退化为文件排序 - 业务需要统计总条数:延迟关联本身不解决
COUNT(*),而SQL_CALC_FOUND_ROWS在 MySQL 8.0+ 已废弃,应改用单独的SELECT COUNT(*)配合缓存或估算 - 分页字段非单调(如多个记录
created_at完全相同):子查询可能因排序不稳定返回不同id序列,造成漏数据或重复
对比游标分页,延迟关联适合什么情况
延迟关联是“妥协方案”——它保留了跳页能力(比如直接查第 500 页),但代价是性能随 offset 增长而缓慢下降;游标分页性能恒定,但只能下一页/上一页。选哪个,取决于业务是否允许放弃随机跳转。
- 后台管理类系统(需支持输入页码、搜索后跳转):优先用延迟关联 + 合理索引
- 用户端瀑布流、消息列表:直接上游标分页,用
WHERE (created_at, id) > ('2024-01-01', 999)这类复合条件防重复 - 混合场景(前 100 页支持跳页,之后强制滚动):可在应用层做路由,小 offset 用延迟关联,大 offset 自动切到游标逻辑
真正容易被忽略的是索引字段顺序和 NULL 值行为——哪怕只差一个字段位置,EXPLAIN 里也可能出现 Using filesort 或 Using temporary,那就白搭了。










