MySQL大偏移量分页性能差,因需扫描offset行再取size行;查第100万页可能扫千万行,致超时、高CPU、主从延迟;应改用游标分页,基于上页末条排序值查询。

MySQL 分页为什么不能直接用 LIMIT offset, size 跳大页?
因为 OFFSET 越大,MySQL 扫描的行数越多——它得先跳过前面所有 offset 行,再取 size 行。查第 100 万页(OFFSET 10000000)时,可能要扫描上千万行,响应慢、锁表久、主从延迟飙升。
常见错误现象:SELECT * FROM orders ORDER BY id DESC LIMIT 9999990, 20 执行超时、CPU 拉满、慢查询日志频繁报警。
- 适用场景:后台管理页、数据导出等允许低频访问的分页;前端用户高频翻页(如商品列表)必须换策略
-
ORDER BY字段必须有索引,否则OFFSET扫描更不可控 - 如果表有
WHERE条件,确保该条件字段也参与索引设计,避免全表扫描后再分页
用游标分页(cursor-based pagination)替代 offset
核心思路:不记“跳过多少行”,而记“上一页最后一条的排序值”。下一页直接查 WHERE sort_col ,跳过扫描,走索引范围查询。
示例(按时间倒序查文章):
created_at
- 只适用于「顺序/倒序 + 稳定排序字段」场景,不支持任意页码跳转(比如用户输“跳到第 87 页”)
- 前端需把上一页末尾的
created_at和id一起传回来,不能只传页码 - 无法跳转中间页,但对用户体验影响小——用户很少手动输大页码,更多是“下一页”“上一页”
需要支持随机页码跳转时,怎么安全地用 LIMIT OFFSET?
真要支持跳转,就别硬扛大 offset。可以加两层限制:一是限制最大可跳页码(比如最多到第 200 页),二是用覆盖索引减少回表成本。
例如,只查主键再关联原表:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
SELECT a.* FROM articles a
INNER JOIN (
SELECT id FROM articles
ORDER BY id DESC
LIMIT 10000, 20
) AS tmp ON a.id = tmp.id;
这样外层 JOIN 只走主键索引,避免扫描全文。
- 最大
OFFSET建议控制在 10000 以内,对应约 500 页(每页 20 条);超出就提示“暂不支持过深翻页” - 务必在
ORDER BY字段建联合索引,如INDEX idx_order (status, created_at)(如果带WHERE status=1) - 不要用
SQL_CALC_FOUND_ROWS,它会强制扫描全结果集;总数单独用COUNT(*)缓存或异步统计
MySQL 8.0+ 的窗口函数能简化分页逻辑吗?
能写,但不推荐用于生产分页。比如用 ROW_NUMBER():
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY id DESC) AS rn
FROM articles
) t WHERE t.rn BETWEEN 10001 AND 10020;
看起来直观,实际性能比 LIMIT OFFSET 更差——它必须给全表每行算序号,内存和临时表压力更大。
- 仅适合一次性分析、后台导出等低频场景
- 如果非要上,记得加
WHERE过滤再开窗,别在子查询里无条件全表开窗 - 和 offset 一样,无法规避大偏移量的扫描代价,只是语法糖
分页不是加个 LIMIT 就完事。真正卡住的从来不是语法,而是 MySQL 怎么找数据——它不会“跳”,只会“扫”。选游标还是限页码,本质是在用户体验和数据库负载之间划那条线。










