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 ),彻底避开 <code>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 页就自动换策略。
