LIMIT offset, size 深分页性能差是因MySQL需全量排序后跳过offset行;应改用延迟关联(先查ID再JOIN)或书签分页(WHERE条件+唯一排序),并确保索引匹配WHERE和ORDER BY顺序。

直接用 LIMIT offset, size 查千万级数据,查到第100页就开始卡,到第1000页基本不可用——这不是代码写得不好,是MySQL执行机制决定的。必须换思路,不能靠调大timeout或加索引硬扛。
为什么 LIMIT 1000000, 20 会慢成这样
MySQL不是“跳到第1000001行读20条”,而是:先按 ORDER BY 字段把所有匹配行排好序(可能上百万行),再从头数1000020个,丢掉前1000000个,返回剩下20个。
- 排序本身要内存/磁盘临时表,
Using filesort出现在EXPLAIN的Extra列里就是信号 - 如果
ORDER BY字段没索引,或用了函数(比如WHERE DATE(created_at) = '2026-01-01'),直接全表扫 -
SELECT *加剧问题:哪怕只想要20条,也要把每行所有字段从磁盘读出来、网络传回去、客户端解包
延迟关联法(JOIN 子查询)怎么写才有效
核心是“先拿ID,再查详情”,让内层只走索引,外层精准回表。但容易写错导致失效。
- 子查询必须只查
id(或排序字段+主键),且ORDER BY字段要有索引,否则子查询自己就Using filesort - 外层
JOIN的关联字段必须是主键(或唯一索引),否则会变成ALL扫描 - 示例正确写法:
SELECT t.* FROM orders t JOIN (SELECT id FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 1000000, 20) tmp ON t.id = tmp.id;
- 错误写法(子查询带
*或非索引字段):SELECT * FROM orders WHERE status = 1 ORDER BY created_at DESC LIMIT 1000000, 20—— 这和原始写法没区别
书签分页(WHERE id > ?)的边界陷阱
适合后台列表“下一页”连续翻,但对跳页、排序变更、数据实时增删很敏感。
- 必须确保排序字段组合能唯一确定一行,否则漏数据或重复。比如按
created_at DESC, id DESC排序,上一页最后一条是(created_at='2026-08-10 12:00:00', id=9999),下一页条件就得写:WHERE created_at <= '2026-08-10 12:00:00' AND (created_at < '2026-08-10 12:00:00' OR id < 9999) ORDER BY created_at DESC, id DESC LIMIT 20
- 如果业务允许,优先用自增主键
id排序最稳:WHERE id > 9999 ORDER BY id LIMIT 20 - 千万别用
WHERE created_at > ?单条件——同一秒插入多条时必然漏数据
真正容易被忽略的点
优化不是选一个SQL替换就完事。实际线上出问题,往往卡在更底层:
-
EXPLAIN看到type是ALL或index,说明WHERE条件根本没走索引,先解决这个,分页优化是空中楼阁 - 复合索引顺序必须匹配
WHERE + ORDER BY字段顺序,比如WHERE status = 1 ORDER BY created_at DESC,索引得建在(status, created_at),反过来无效 - MySQL 5.7 默认
sort_buffer_size很小,大结果集排序容易落盘,查SHOW VARIABLES LIKE 'sort_buffer_size';,必要时调高(但别设太大,会撑爆内存)


















