MySQL分页首选LIMIT offset, size,但需注意OFFSET从0开始、大OFFSET性能差;深分页应改用游标分页(WHERE id > last_id ORDER BY id LIMIT size),要求排序字段唯一单调,不支持跳页。

MySQL分页用 LIMIT 和 OFFSET 最直接
绝大多数场景下,SELECT ... LIMIT offset, size 是唯一实用的分页写法。它简单、语义清晰,且被所有 MySQL 版本支持。
常见错误是把 OFFSET 当成页码直接乘:比如第 100 页、每页 20 条,写成 LIMIT 100, 20 —— 这实际跳过了前 100 行,不是第 100 页。正确应是 LIMIT 1980, 20(即 (100-1)*20)。
-
OFFSET从 0 开始计数,第 1 页对应OFFSET 0 - 避免在大表上用大
OFFSET(如 >10 万),MySQL 仍要扫描前面所有行,性能陡降 - 如果排序字段有重复值,
ORDER BY必须包含唯一列(如主键),否则同一页结果可能错乱或漏数据
深分页慢?试试基于游标的分页(WHERE id > ? LIMIT size)
当 OFFSET 超过几万,LIMIT + OFFSET 就会明显变慢。这时改用“游标分页”:记住上一页最后一条的主键值,下一页查 WHERE id > last_id ORDER BY id LIMIT size。
这种写法能走索引,不依赖扫描行数,速度稳定。但它要求排序字段单调、唯一、可比较(推荐用自增 id 或时间戳+唯一ID组合)。
- 不能跳页(比如直接跳到第 500 页),只支持“下一页”“上一页”
- 必须确保
ORDER BY字段在WHERE中参与过滤,否则索引失效 - 如果业务允许,优先用
WHERE created_at > ? AND created_at 分时间段代替纯偏移
SQL_CALC_FOUND_ROWS 已被弃用,别再用
MySQL 8.0.17 起彻底移除了 SQL_CALC_FOUND_ROWS,旧代码里类似 SELECT SQL_CALC_FOUND_ROWS * FROM t LIMIT 10 会报错。
现在要总数,必须单独发一条 SELECT COUNT(*) 查询。虽然多一次请求,但更可控、更清晰。
- 不要试图在应用层缓存总数来规避查询——数据变更后容易不一致
- 如果总数不重要(如无限滚动),干脆不查总数,前端只显示“加载更多”
- 对超大表,
COUNT(*)本身也可能慢;可考虑估算(SHOW TABLE STATUS的Rows字段)或异步更新统计值
ORDER BY + LIMIT 组合必须加复合索引
哪怕只是 ORDER BY created_at DESC LIMIT 20,如果没有对应索引,MySQL 就得全表排序,再取前 20 —— 数据量一大就卡死。
正确做法是建覆盖排序和查询字段的索引,例如:CREATE INDEX idx_created_id ON posts(created_at DESC, id DESC)。这样 ORDER BY created_at DESC, id DESC LIMIT 20 就能直接走索引。
- 注意 ASC/DESC 在索引定义中要与查询一致(MySQL 8.0+ 支持混合方向,老版本建议统一用 DESC)
- 如果查询还带
WHERE条件(如WHERE status = 'published'),索引要把该字段放在最左列 - 用
EXPLAIN看type是否为range或index,Extra里不能有Using filesort或Using temporary
游标分页的健壮性远高于偏移分页,但需要业务逻辑配合;而索引设计不是“加上就行”,必须贴合实际查询模式。这两点最容易被忽略,也最难事后补救。


















