应避免使用limit offset, size进行百万级数据分页,因其需扫描并丢弃前offset行;推荐游标分页(基于唯一单调字段如id或created_at)或延迟关联优化,并配合正确索引设计。

直接用 LIMIT offset, size 查百万级数据的第1000页?别干了——它会扫完整个前 1000×size 行再丢弃,性能断崖式下跌。真正能扛住高偏移量的分页,靠的不是调优参数,而是绕开 OFFSET 本身。
为什么 LIMIT 1000000, 20 会慢得离谱
MySQL 不会“跳到第 1000001 行”,而是:先排序全部匹配行 → 按顺序逐行计数 → 数到 1000020 → 扔掉前 1000000 条 → 返回剩下 20 条。这意味着:
- 哪怕只查 20 条,也要读取并处理 1000020 行(含磁盘 I/O、内存排序、临时文件)
- 如果
ORDER BY字段没索引,触发filesort,性能雪崩 -
SELECT *会让回表次数翻倍(尤其非主键索引 + 大字段) -
EXPLAIN中看到type = ALL或rows接近总行数,基本就是全表扫描了
用游标分页替代 OFFSET(推荐首选)
核心是把“第 N 页”变成“从某个已知值之后取 N 条”,让 MySQL 能用索引快速定位起点。前提是排序字段必须有唯一性+单调性(如自增 id、created_at):
- 第一页:
SELECT id, title, created_at FROM posts ORDER BY created_at DESC LIMIT 20; - 第二页(假设上一页最后一条
created_at = '2026-06-28 14:30:00'):SELECT id, title, created_at FROM posts WHERE created_at - 必须加
WHERE条件且和ORDER BY字段一致,否则可能漏数据或重复 - 不能用
BETWEEN或>+ORDER BY ... ASC混搭,容易因并发插入导致边界错位
延迟关联优化(兼容传统页码场景)
当业务强依赖“第 N 页”语义(比如后台管理页码输入框),又无法改游标时,用两层查询拆解 I/O 压力:
- 内层只查主键(走覆盖索引,极快):
SELECT id FROM posts ORDER BY id LIMIT 1000000, 20; - 外层用主键精确 JOIN:
SELECT p.* FROM posts p JOIN (SELECT id FROM posts ORDER BY id LIMIT 1000000, 20) tmp ON p.id = tmp.id; - 必须确保
ORDER BY字段是主键或有联合索引(如INDEX (status, id)),否则内层仍会慢 - 如果原查询带
WHERE条件,务必在内层子查询里也加上,否则结果不一致
最容易被忽略的坑:索引设计与字段选择
再好的分页写法,没对的索引也是白搭:
- 游标分页必须在
WHERE + ORDER BY字段建联合索引,例如INDEX (status, created_at),而非单独建created_at索引 - 延迟关联中,内层
SELECT id必须能走索引;如果查的是SELECT name,就失去覆盖索引优势 - 避免在分页查询里
SELECT *,只取真实需要的字段——大文本字段(TEXT、JSON)会显著拖慢传输和内存拷贝 - 时间字段做游标时,注意精度:用
DATETIME(3)就别只传秒级字符串,否则同秒多条记录会漏
游标分页不是万能的——它要求前端记住上一页末尾值,且不支持跳转任意页;延迟关联能保页码但仍有上限。真正要撑住千万级数据的分页,得从接口设计源头放弃“总页数”和“跳转到第 N 页”的幻想,用无限滚动或搜索替代。











