offset越大越慢是事实,因MySQL必须先跳过前offset行再取count行,即使有索引也无法跳过逻辑偏移;排序不稳定时需加主键保证顺序。

limit offset,count 里 offset 越大越慢,不是错觉
MySQL 的 LIMIT offset,count 执行时,**必须先跳过前 offset 行**,再取 count 行。这意味着 SELECT * FROM orders LIMIT 100000,20 实际上要扫描至少 100020 行(甚至更多,取决于索引覆盖情况),而不仅仅是返回最后 20 行。
- 即使有索引,只要
ORDER BY字段没走索引或查询字段未被覆盖,MySQL 仍需回表逐行计数跳过 - 偏移量为 0(如
LIMIT 0,20)几乎无开销;偏移量到 50 万以上时,响应可能从毫秒级升至秒级 - 典型错误认知:“加了索引就没事”——错。索引能加速定位,但无法跳过逻辑上的“跳过 offset 行”这一步
limit 一个参数 vs 两个参数:语义等价但写法习惯不同
LIMIT n 和 LIMIT 0,n 功能完全一样,都表示取前 n 行;但它们在分页上下文中容易引发混淆。
-
LIMIT 10→ 安全、简洁,适合首页或“取最新 10 条”场景 -
LIMIT 20,10→ 明确表达“跳过 20 行,取 10 行”,是标准分页写法,但 offset 必须是常量,不能是表达式(比如LIMIT (page-1)*size, size在 SQL 层直接报错) - 应用层拼接时务必校验
offset≥ 0,负数会直接触发ERROR 1064(例如LIMIT -1,10或LIMIT 5,-1全部非法)
MySQL 分页的隐性规则:排序不稳定会导致数据重复或丢失
当多行记录在 ORDER BY 字段上值相同时(比如按 created_at 排序,但有几十条同秒插入的订单),MySQL 不保证这些行之间的相对顺序。下一页可能跳过某些行,或重复出现上一页的某几条。
- 解决办法:在
ORDER BY后追加主键(或唯一非空列),例如ORDER BY created_at DESC, id DESC - 不要依赖“自然顺序”或“插入顺序”——InnoDB 中没有绝对的物理顺序保障
- 如果业务允许,优先用游标分页(
WHERE created_at ),彻底避开OFFSET
兼容 PostgreSQL 的写法:LIMIT … OFFSET 更清晰但不更高效
MySQL 支持 LIMIT count OFFSET offset 语法(如 LIMIT 10 OFFSET 20),和 LIMIT 20,10 完全等价,只是参数顺序调换、语义更直白。
- 好处:SQL 可读性略高,且方便跨数据库迁移(尤其对接 pg 的 ORM)
- 坏处:同样受 offset 性能惩罚,且部分旧版客户端驱动对这种写法解析不稳定
- 注意:
OFFSET后面不能跟负数,也不能是变量或子查询结果,仍是硬编码整数常量
SELECT id, title, created_at FROM articles WHERE status = 'published' ORDER BY created_at DESC, id DESC LIMIT 20 OFFSET 40;
分页真正难的从来不是语法,而是 offset 增长后 MySQL 仍坚持“老老实实数数”这个行为——它不会因为你翻到了第 1000 页就自动换策略。










