LIMIT 100000, 20 极慢是因为 MySQL 必须从头扫描并回表 100020 次,丢弃前 100000 行,触发大量随机 IO;应改用延迟关联或游标分页。
为什么
会慢得离谱
MySQL 执行
时,并不是直接跳到第
行再取数据,而是从头扫描,逐行计数,直到累计读够
行,再丢掉前
行。当
,它就得回表 100020 次——哪怕你只想要最后 20 条。二级索引查 ID、再用 ID 回主键索引取整行,每一步都在触发随机 IO,缓存命中率暴跌,性能断崖式下滑。
怎么写才真正生效
延迟关联的核心是“先轻后重”:子查询只走索引取
,外层再按主键精准拉数据。但必须满足几个硬条件,否则照样慢:
子查询的
字段必须有**覆盖索引**(比如
,对应联合索引
),否则子查询本身就会全表扫描
外层
的关联字段必须是主键或唯一键,否则 MySQL 可能放弃使用
访问类型,退化为
或
避免在子查询里
或带非索引字段,否则无法触发
,EXPLAIN 的
列会显示
示例正确写法:
和
对比,哪种更适合你的场景
两者都绕开了大 offset,但适用边界很不同:
MySQL(Linux)
MySQL 9.6.0是面向Linux平台的2026年创新版本,核心架构迎来重大革新。其将外键约束与级联操作从InnoDB引擎层上移至SQL层,确保所有数据变更均被完整记录至Binlog,彻底解决了CDC(变更数据捕获)与主从复制中的数据不一致难题。此外,该版本引入container_aware启动选项以原生适配容器环境,并对审计日志进行了组件化重构,为追求极致数据一致性与云原生体验的开发者提供了全新选择。
下载
要求排序字段与主键强相关(如自增
),且数据不能大量删除,否则页码错位;适合简单主键分页,但不支持多条件过滤 + 复合排序(比如
)
延迟关联对排序字段无强依赖,只要该排序能走索引(哪怕是联合索引),就能稳定生效;支持任意
条件 +
组合,业务适配性更强
性能上,延迟关联在
时通常更稳;而子查询取单个起始
的方式,在高并发下可能因子查询被重复执行多次带来额外开销
注意:如果业务允许“只能下一页”,优先用游标式(
),它比两种方案都快一个数量级
容易被忽略的三个坑
延迟关联不是套个
就万事大吉,真实线上踩过这些坑的人才知道:
没加
时,MySQL 优化器可能把外层表当驱动表,导致全表扫描;强制指定顺序:
子查询结果集若包含重复
(比如
或
没处理好),外层
会放大结果行数,甚至翻倍
如果分页字段存在大量相同值(如千万条记录
),
中的
区分度太低,B+ 树遍历时仍要比较大量
,此时必须补上足够区分的第二排序字段,否则延迟关联优势大幅缩水
实际效果取决于索引设计是否真正覆盖了子查询路径,而不是 SQL 表面上用了
。很多团队测完发现没提速,问题八成出在子查询那一步根本没走索引。
LIMIT 100000, 20LIMIT offset, sizeoffsetoffset + sizeoffsetoffset = 100000SELECT * FROM t JOIN (SELECT id FROM t ... LIMIT N,M) AS tmp ON t.id = tmp.ididORDER BYORDER BY create_time DESC, id DESCKEY idx_ct_id (create_time, id)JOINeq_refALLrangeSELECT *Using indexExtraUsing where; Using filesortSELECT a.* FROM articles a
INNER JOIN (
SELECT id FROM articles
WHERE status = 1
ORDER BY created_at DESC, id DESC
LIMIT 100000, 20
) AS tmp ON a.id = tmp.id;WHERE id >= (SELECT id FROM ... LIMIT N,1)WHERE id >= (SELECT ... LIMIT N,1)idORDER BY category, updated_atWHEREORDER BYoffset > 50widWHERE created_at < ? AND id < ?JOINSTRAIGHT_JOINSELECT STRAIGHT_JOIN a.* FROM (SELECT id ...) AS tmp JOIN articles a ON a.id = tmp.ididGROUP BYDISTINCTJOINstatus = 1ORDER BY status, idstatusidJOIN