MySQL执行LIMIT offset,size时并非跳转至第offset行,而是从头扫描并累计读取offset+size行后丢弃前offset行,导致深分页性能随offset线性下降;加索引无法改变该语义,需用覆盖索引或游标分页优化。

因为 MySQL 不是“跳到第 N 行”,而是“从头开始读,数够 N+size 行,再丢掉前 N 行”——这个行为在底层无法绕过,不是加索引或调优能解决的。
MySQL 执行 LIMIT offset, size 的真实过程
你写 LIMIT 1000000, 20,MySQL 实际干了三件事:
- 扫描满足
WHERE条件的所有行(或索引项),按ORDER BY排序生成完整结果集 - 逐行计数,直到累计读出 1000020 条记录
- 丢弃前 1000000 条,只把最后 20 条返回给客户端
关键点在于:**丢弃的那 100 万行,该扫描的照样扫描,该回表的照样回表,该排序的照样排序**。哪怕你只查 id,只要没走覆盖索引,每丢一行都可能触发一次随机 I/O。
EXPLAIN 看不出问题?rows 字段才是重点
很多人看 EXPLAIN 发现 type 是 range 就以为没问题,但真正致命的是 rows 值:
- 如果
rows = 1200000,说明 MySQL 认为要扫描约 120 万行才能凑够 1000020 条有效记录 - 这个数字会随
offset线性增长,翻倍 offset,基本就翻倍耗时 - 如果
Extra里出现Using filesort或Using temporary,说明连排序都在磁盘上做,性能雪上加霜
别信 type,盯紧 rows 和 Extra —— 它们才是深分页是否已失控的体温计。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
为什么加索引有时也没用?
索引能加速,但不能改变语义。常见误区:
- 只给
ORDER BY字段建单列索引(比如INDEX(create_time)):WHERE 条件不走索引,还是全表扫 - 联合索引顺序错(比如建了
INDEX(status, id, create_time),但查询是WHERE status = 1 ORDER BY create_time DESC):索引无法同时满足过滤和排序 - 索引包含字段太多或太宽(比如含
VARCHAR(500)):B+ 树层级变深,定位反而更慢
正确姿势是建 INDEX(status, create_time, id) 这类“过滤+排序+主键”三元组索引,且确保子查询里只查这三列(尤其是 id 必须放在最后)。
游标分页为什么快?但它有个硬约束
游标分页(WHERE id )快,是因为它把“找第 N 行”变成了“找大于 X 的最小 20 行”:
- 起点可直接用主键索引
O(log n)定位,不用数数 - 后续 20 行是连续物理存储(如果是自增主键),顺序读即可
但它要求排序字段必须唯一、单调、有索引。用 create_time 当游标?得组合 (create_time, id),否则同时间戳的记录会导致漏/重。另外,删数据或并发插入会让游标“断档”,没法跳页——这些不是 bug,是设计取舍。
偏移量越大,MySQL 越老实;越想跳着走,越得换思路。真正的优化不在 SQL 写法本身,而在你愿不愿意放弃“第 N 页”这个业务概念,改用“下一页”这个数据库友好概念。

















