分页查询的优化方法(MySQL 高性能分页技巧)
分页在项目中非常常见,例如:
SELECT * FROM article ORDER BY id LIMIT 1000000, 10;
但越往后翻页,性能越差:
-
• MySQL 会扫描 前 100 万行 丢弃掉 -
• 只返回最后 10 行 -
• 导致 CPU 飙升、耗时高、IO 大
下面总结 最常用、最有效的分页优化方法。
一、避免深分页:使用索引字段做“记号分页”
这是最推荐,也是互联网公司最常用的方式。
原 SQL:
LIMIT 1000000, 10
优化:使用上次的最大 id 继续往后查
SELECT * FROM article
WHERE id > last_id
ORDER BY id
LIMIT 10;
优点:
✔ 不扫描前面的数据
✔ 不会随着页数变大而变慢
✔ 尤其适合时间流、日志流、推文流
最适合以下场景:
-
• 上拉加载更多 -
• 无限滚动 -
• 移动端 feed 流
二、使用覆盖索引(减少回表)
深分页最大成本 = 扫描 + 回表。
例如:
SELECT * FROM article ORDER BY id LIMIT 100000, 10;
优化方式:
SELECT id FROM article ORDER BY id LIMIT 100000, 10;
然后再根据 id 去主键表查:
SELECT * FROM article WHERE id IN (...);
这种方式叫 二次查询法,优点:
✔ 首次扫描使用覆盖索引,速度极快(不用回表)
✔ 第二次只查 10 条,非常轻量
三、使用“子查询 + join”优化深分页
经典分页优化:
SELECT * FROM article
WHERE id IN (
SELECT id FROM article
ORDER BY id LIMIT 100000, 10
);
或使用 join:
SELECT a.*
FROM article a
JOIN (
SELECT id
FROM article
ORDER BY id
LIMIT 100000, 10
) b ON a.id = b.id;
子查询 LIMIT 使用覆盖索引,性能极高。
四、限制可访问分页深度(后台管理常用)
例如限制为 1000 页:
if (page > 1000) {
return error("不支持更深分页");
}
Pinterest / Instagram 等都使用类似策略:
避免用户翻 50 万页,本质没意义,也非常耗资源。
五、提前过滤减少数据量
例如:
原分页:
SELECT * FROM order LIMIT 1000000, 10;
可以加上条件:
SELECT * FROM order
WHERE create_time >= '2023-01-01'
ORDER BY id
LIMIT 10;
减少扫描量,就能减少分页成本。
六、使用延迟关联(最推荐的深分页终极优化)
延迟关联 = 先用索引查主键,再与主表 join
例子:
SELECT *
FROM article a
JOIN (
SELECT id
FROM article
WHERE status=1
ORDER BY id
LIMIT 1000000, 10
) t
ON a.id = t.id;
延迟关联的特点:
✔ 子查询能用覆盖索引
✔ 不用扫描大表的所有列
✔ 主表只读取 10 行
实际效果:
100 万页从几秒降到几十毫秒。
七、使用倒排分页(从后往前查)
如果用户只看最后几页,可以:
SELECT *
FROM article
ORDER BY id DESC
LIMIT 10;
这个是高效的,不是深分页。
只要不是 OFFSET 大,MySQL 就快。
八、缓存分页接口(特别是热门页)
例如 Redis:
-
• 第 1 页数据固定最热门,完全可以缓存 -
• 某些榜单分页可以整体缓存
缓存可以让分页查询直接变成 O(1)。
九、使用搜索引擎或 ES(超大量数据)
当数据量达到千万级甚至亿级,MySQL 分页本身就不是最佳方案。
通常改用:
-
• Elasticsearch -
• Solr -
• ClickHouse -
• OLAP 系统
这些系统分页查找更高效。
总结
1、深分页之所以慢,是因为 MySQL 会扫描大量不需要的数据。
2、 核心优化思路就是 减少扫描 + 避免回表。
3、 最有效的方法是:
游标分页(id > last_id) + 覆盖索引 + 延迟关联。

