LIMIT offset, size 越往后越慢,因为MySQL需扫描并丢弃前offset行,造成大量无效索引遍历和回表;优化方案是用主键或时间字段范围查询替代OFFSET,如WHERE id > 上一页最大id ORDER BY id LIMIT 20。

为什么 LIMIT offset, size 会越来越慢
因为 MySQL 每次执行 LIMIT 100000, 20 这类语句时,必须先扫描前 100000 行(哪怕全丢掉),再取后面 20 行。数据量越大、页码越靠后,扫描成本越高——这不是“跳转”,而是“数数”。即使有主键索引,OFFSET 仍会导致大量无效的索引遍历和回表。
用主键范围查询替代 OFFSET
前提是表有单调递增的主键(如 id INT AUTO_INCREMENT)或业务上有严格有序的时间字段(如 created_at)。核心思路是:不跳行,而是“记住上一页最后一条的值”,下一页直接从它之后查。
- 上一页最后一条是
id = 8000001,下一页就写:SELECT * FROM t_order WHERE id > 8000001 ORDER BY id ASC LIMIT 20 - 如果按时间倒序分页,上一页最后一条
created_at = '2025-11-05 14:22:03',下一页写:SELECT * FROM t_order WHERE created_at - 必须确保
WHERE字段和ORDER BY字段一致,且该字段有索引;否则退化为全表扫描 - 不能用于“跳转到第 1000 页”这种随机页需求,只适合连续下拉翻页场景
用覆盖索引 + JOIN 减少回表开销
当无法改用范围查询,又必须支持大 offset 时,优先让 MySQL 只在索引里“数数”,再用主键去捞完整数据。
- 先用覆盖索引查出 ID:
SELECT id FROM t_order ORDER BY id LIMIT 100000, 20(快,因只走主键索引) - 再用这些 ID 关联原表:
SELECT a.* FROM t_order a JOIN (SELECT id FROM t_order ORDER BY id LIMIT 100000, 20) b ON a.id = b.id - 比直接
LIMIT 100000, 20快一个数量级,尤其当表宽(列多、TEXT/BLOB 多)时效果更明显 - 注意:子查询里必须显式写
ORDER BY,否则结果不稳定
容易被忽略的关键细节
很多优化失效,不是语法写错,而是踩在了隐含约束上:
-
ORDER BY的字段必须是索引最左前缀,否则范围查询或覆盖索引都会失效 - 使用时间字段分页时,若存在秒级重复值(比如批量插入),
WHERE created_at 可能漏数据或重复——得加二级条件,如 <code>AND id - UUID 主键理论上可用,但顺序性差,索引效率低,且分页结果易跳跃;不建议作为分页依据
- 应用层必须持久化“游标值”(上一页最后的
id或created_at),不能每次从页码算 offset
实际分页逻辑不在 SQL 里,而在你如何设计游标和传递上下文。偏移量是幻觉,位置才是真相。


















