MySQL深分页变慢主因是优化器需扫描前offset+size行,游标分页(WHERE+联合索引)可跳过扫描,覆盖索引和JOIN子查询为次优方案。

为什么 LIMIT offset, size 越往后越慢
MySQL 深分页变慢,根本不是因为 LIMIT 本身,而是因为优化器必须先扫描前 offset + size 行才能定位到结果——哪怕你只想要最后 20 条。如果 offset 是 100 万,InnoDB 就得逐行读取并丢弃前 100 万行(即使有索引),I/O 和 CPU 都在白忙。
典型表现:EXPLAIN 显示 rows 极高,type 是 index 或 range,但 Extra 出现 Using filesort 或干脆没走索引(比如 ORDER BY 字段没索引)。
关键前提:必须有覆盖排序和过滤的联合索引,否则所有优化都无效。
用游标分页替代 OFFSET(推荐方案)
本质是把“我要第 N 页”改成“我要比上一页最后一条更新的数据”,跳过扫描,直接 WHERE 定位起始点。
适用场景:用户持续下拉、时间线类列表、后台分批导出等——要求排序字段唯一或能组合成唯一标识(如 (created_at, id))。
- 假设按
created_at DESC, id DESC排序,上一页最后一条是created_at = '2024-05-01 10:20:30', id = 12345,下一页查询写成:SELECT * FROM orders WHERE (created_at, id)
- 必须为
(created_at, id)建联合索引,且顺序与ORDER BY完全一致;和DESC方向要匹配,否则索引失效 - 不能跳页(比如从第 1 页直接跳第 100 页),但对绝大多数真实场景影响不大
强制用索引 + 覆盖索引减少回表
当无法改成分页逻辑(比如老系统、第三方接口强依赖 OFFSET),至少让 MySQL 少做无用功。
常见错误:只给 ORDER BY 字段建索引,但 SELECT * 导致大量回表,深分页时随机 I/O 爆增。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 建覆盖索引:比如查
SELECT id, title, created_at FROM article ORDER BY created_at DESC,索引应为INDEX idx_created_id (created_at, id)(注意顺序!created_at在前) - 如果必须返回更多字段,考虑用
SELECT id先查主键,再用IN二次查询——但要注意IN列表长度限制和性能拐点(一般不超过 500 个 ID) - 避免在深分页场景用
SELECT *,明确列出需要字段,尤其避开TEXT/BLOB类型
JOIN 子查询优化法(仅限小数据量补救)
原理是先用子查询快速拿到目标页的主键,再关联原表取完整数据,把大范围扫描压缩成主键范围扫描。
适用于:单表深分页、主键连续性较好、业务允许轻微延迟(子查询仍需扫 offset 行,但只扫索引)。
示例(查第 100001–100020 条):
SELECT a.* FROM article a
INNER JOIN (
SELECT id FROM article
ORDER BY created_at DESC
LIMIT 100000, 20
) AS b ON a.id = b.id;
注意:INNER JOIN 内子查询必须只 SELECT id(或其他被索引的列),且外层 a.id = b.id 必须有主键或唯一索引支持,否则关联会变慢;MySQL 8.0+ 对这种写法优化更好,5.7 下效果有限。
真正棘手的不是语法怎么写,而是业务是否接受放弃传统页码——游标分页要前端配合传递上一页末位标识,这个协作成本常被低估。










