OFFSET越大查询越慢,因MySQL需扫描并丢弃前N行而非跳过;游标分页和延迟关联可避免深度偏移,将不可伸缩的“跳行”转为可索引的“定位”。
MySQL 的 LIMIT offset, size 在 offset 很大时(比如 LIMIT 1000000, 20),实际仍会扫描前 1000020 行,再丢弃前 1000000 行——这不是跳过,而是「读取后过滤」。InnoDB 没有跳转到第 N 行的物理索引能力,尤其当排序字段无索引或使用 ORDER BY RAND() 时,性能断崖式下跌。
Using filesort)会频繁触发磁盘 I/OWHERE 条件未命中索引,MySQL 可能先回表查主键,再排序,再跳 offset,三重开销EXPLAIN 中 rows 值远大于 limit,就是危险信号适用于按时间、ID 等单调递增字段排序的场景,核心是「记住上一页最后一条的值」,下一页直接 WHERE id > last_id LIMIT 20。它不依赖行号,避免深度偏移。
INDEX (created_at, id)),且查询条件能命中该索引最左前缀created_at 相同),需加入二级排序字段(如 id)保证结果稳定SELECT * FROM orders
WHERE created_at > '2025-01-01 12:00:00'
AND (created_at, id) > ('2025-01-01 12:00:00', 55678)
ORDER
BY created_at, id
LIMIT 20;当需要分页返回宽表字段(如含 TEXT、BLOB),但排序只依赖少量字段时,先用 LIMIT 查出主键,再用主键二次 JOIN 获取完整数据,可大幅减少临时表和回表量。
SELECT * FROM users WHERE status=1 ORDER BY created_at DESC LIMIT 100000, 20 —— 回表 100020 次status 和 created_at 组成联合索引,否则子查询仍慢SELECT u.* FROM users u
INNER JOIN (
SELECT id FROM users
WHERE status = 1
ORDER BY created_at DESC
LIMIT 100000, 20
) AS t ON u.id = t.id;即使写了 ORDER BY indexed_col,MySQL 优化器也可能因统计信息过期或数据倾斜,放弃走索引而选全表扫描。强制指定索引并确保 SELECT 字段都在索引中(覆盖索引),能锁死高效路径。
CREATE INDEX idx_status_ctime_id ON users(status, created_at, id)
FORCE INDEX,避免优化器误判SELECT id, name, email FROM users FORCE INDEX (idx_status_ctime_id) WHERE status = 1 ORDER BY created_at DESC LIMIT 20;
真正卡住分页性能的,往往不是 LIMIT 本身,而是它暴露了索引缺失、排序字段无序、或查询未收敛到索引范围的问题。游标分页和延迟关联不是银弹,但它们把「跳过多少行」这个不可伸缩的操作,转化成了「找到下一个位置」这个可索引的操作——这才是数据库擅长的事。