mysql limit offset, size越往后越慢,是因为其需先排序并逐行扫描前offset+size行,再丢弃前offset行,导致io和cpu开销随偏移量线性增长;应改用游标分页,即以排序字段(如id)为锚点,通过where条件过滤后直接limit查询,避免offset。

LIMIT 分页不是“写对语法就能用好”,偏移量一大,查询就卡死——核心问题不在 LIMIT 本身,而在 MySQL 扫描机制。
为什么 LIMIT offset, size 查询越往后越慢?
MySQL 并不会跳过前 offset 行再读 size 行,而是先按 ORDER BY 排序(如果有的话),然后从头逐行扫描,计数到 offset + size 才停。比如 LIMIT 999990, 10,它得扫描并丢弃 999990 行,只留最后 10 条。
- 没有
ORDER BY时,顺序不可靠,分页结果可能错乱或重复 -
offset超过 10 万后,响应时间通常呈线性增长,不是常数级 - 即使有索引,只要涉及大偏移量,InnoDB 仍需回表或遍历索引树多次
怎么算 offset 和 size 才不出错?
分页参数必须从 0 开始计算,且不能依赖用户传入的“页码”直接乘除——要防整数溢出、负值、超界。
- 第
P页(从 1 开始)、每页N条 →OFFSET = (P - 1) * N,LIMIT = N - 但
P必须校验:P ,否则查不到数据还浪费资源 - 更安全的做法是:先用
COUNT(*)或估算值判断总页数,再决定是否执行分页查询 -
OFFSET不能是表达式,如LIMIT 10 OFFSET @page * 20会报错;必须预计算为具体整数
大表分页必须避开 OFFSET 的三种写法
当 OFFSET > 10000 时,应放弃 LIMIT ... OFFSET,改用基于游标(cursor-based)的分页逻辑。
- 用主键或唯一有序字段做锚点:
SELECT * FROM orders WHERE id > 123456 ORDER BY id LIMIT 10,上一页末尾的id就是下一页的起点 - 复合排序场景(如
created_at DESC, id DESC)需同时比较两个字段:WHERE (created_at, id) - 避免
ORDER BY RAND() LIMIT N做随机分页——全表排序开销极大;可用WHERE RAND() 先粗筛再取样
ORDER BY 是 LIMIT 分页的前提,不是可选项
没加 ORDER BY 的 LIMIT 查询,在 InnoDB 中返回顺序由聚簇索引物理位置决定,一旦发生页分裂、删除、插入,结果就可能跳跃或重复。
- 正确姿势:
SELECT * FROM users ORDER BY updated_at DESC, id DESC LIMIT 20 OFFSET 40 - 必须确保
ORDER BY字段有索引,否则排序本身就会拖垮性能 - 不要用
SELECT *+ 大LIMIT查宽表——只选必要字段,减少 IO 和网络传输
真正难的不是写出 LIMIT 语句,而是在数据持续写入、偏移量动态增长、前端跳页不可控的情况下,让每一页都稳定、快速、不漏不重。游标分页不是银弹,但它把“跳多少行”的问题,转化成了“从哪个值开始查”的索引查找问题——这才是 MySQL 分页能扛住千万级数据的关键所在。











