mysql limit分页越往后越慢,是因为offset导致mysql需扫描并丢弃前offset行,而非直接跳转索引位置;应改用where id > 上一页最大id order by id limit n的游标分页法。

MySQL LIMIT 分页为什么越往后越慢
因为 LIMIT offset, count 中的 offset 不是跳过索引位置,而是让 MySQL 先扫描并丢弃前 offset 行——哪怕你只想要 20 条,offset 是 100 万,它就得先读满 100 万零 20 行再扔掉前面的。
- 真实场景:用户翻到第 5000 页(
LIMIT 49990, 10),查询明显卡顿,甚至触发慢查询告警 - 根本原因:没有利用索引定位起始点,全靠“数数”推进,I/O 和 CPU 成倍增长
- 不是 LIMIT 本身慢,是
offset大了之后执行计划退化为全表/全索引扫描
用主键或唯一索引代替 OFFSET 实现高效分页
跳过“数行数”,改用“找上一页最后一条的 ID 值”来定位下一页起点。前提是排序字段有索引(通常是主键 id 或时间戳 created_at)。
- 适用场景:列表按
id升序分页,且id连续或基本连续(不强求) - 错误写法:
SELECT * FROM posts LIMIT 10000, 20—— 每次都扫 10020 行 - 正确写法:
SELECT * FROM posts WHERE id > 10000 ORDER BY id LIMIT 20—— 索引直接定位,几乎常量时间 - 注意:必须确保
WHERE条件和ORDER BY字段一致,否则可能漏数据或重复
OFFSET 超过 100 万时 MySQL 可能拒绝执行
某些 MySQL 版本(如 8.0.20+)在 max_seeks_for_key 或优化器成本估算超标时,会放弃使用索引,转而走全表扫描,甚至返回空结果或报错 ER_WARN_TOO_FEW_RECORDS(少见但存在)。
- 典型现象:明明表里有数据,
LIMIT 1200000, 10却查不到任何记录,且执行计划显示type: ALL - 参数影响:
sort_buffer_size太小会导致排序阶段内存不足,触发磁盘临时文件,进一步拖慢 OFFSET 查询 - 兼容性提示:MySQL 5.7 对大 OFFSET 更“宽容”,但性能一样差;MariaDB 同样面临此问题,无本质区别
游标分页(Cursor-based Pagination)怎么写才不翻车
这是生产环境推荐的替代方案,核心是把“第 N 页”变成“从某个确定值之后取 N 条”,彻底绕开 OFFSET。
- 必须包含排序字段 + 唯一性保障字段(如
id),例如:WHERE (created_at, id) > ('2024-01-01', 12345) ORDER BY created_at, id LIMIT 20 - 不能只用
created_at(可能重复),也不能只用id(如果排序逻辑是按时间而非 ID) - 前端需保存上一页最后一条的完整游标值(比如
{"created_at":"2024-01-01 10:00:00","id":12345}),不能只传 ID - 缺点:不支持跳转任意页(比如直接点“第 87 页”),但对无限滚动、下拉加载足够且更稳
实际分页逻辑复杂的地方不在 SQL 写法,而在如何保证游标值的唯一性和排序一致性——一旦业务排序规则变化(比如从 id DESC 改成 updated_at DESC),旧游标就完全失效。











