沉默是金,总会发光
大家好,我是沉默
你有没有被问过这个经典问题:
“如果表里数据量特别大,翻页翻到100万条,怎么优化分页查询?”
别以为这只是简单的 LIMIT + OFFSET,实际上这道题背后藏着对数据库原理理解、系统架构设计能力和实战经验的三重考验。
今天我们就从面试现场出发,带你一步步拆解“深度分页”的天坑,看看为什么它这么难搞,又该如何优雅应对。
**-**01-
问题定位:为什么分页越深越慢?
先别急着堆优化方案,搞清楚底层原理才是技术高手的标配!
LIMIT 不是你想的“跳过N条”
很多人以为:
SELECT * FROM orders ORDERBY id LIMIT 1000000, 10;
真相是:
数据库真的会捞出前1000000+10条数据,然后丢掉前100万,返回你想要的那10条。
并且,如果你用了排序,还要先排序、再回表,性能开销直接爆炸:
-
排序慢:内存排序压力大;
-
回表慢:用了普通索引还得一条条去主键索引捞原始数据;
-
IO压力大:每一次回表 = 一次磁盘IO,10万条回表直接IO风暴。
**-**02-
单表分页如何优化?
核心思路:绕过“数着跳”的笨办法,精准命中目标数据。
方案一:子查询定位起点
SELECT * FROM user WHERE id >= ( SELECT id FROM user ORDER BY age LIMIT 100000, 1)LIMIT 10;
思路:先用覆盖索引快速定位第 100000 条记录的 ID,再直接查目标。
适用场景:ID 连续自增,结果集结构单一。
方案二:JOIN 联表优化回表
SELECT * FROM user t1JOIN ( SELECT id FROM user ORDER BY age LIMIT 100000, 10) t2 ON t1.id = t2.id;
思路:先快速找出目标 ID,再一次性拿详情,避免多次回表。
方案三:索引覆盖查询
ALTER TABLE user ADD INDEX idx_age_name(age, name);SELECT age, name FROM user ORDER BY age LIMIT 100000, 10;
思路:查询字段全部在索引中,无需回表!
**-**03-
分库分表翻页慢?
现在问题来了:
在分库分表之后,分页查询怎么更慢了?
假设你有 6 张分表:
SELECT * FROM orders ORDER BY create_time DESC LIMIT 1000000, 10;
数据库会怎么执行?
每张表都查 1000010 条 × 6 张表 = 捞出 600万+ 数据
→ 汇总 → 全量排序 → 丢前100万 → 留后10条 → 你崩了
三大致命问题:
-
数据分散,全局排序复杂;
-
深分页=分片全量扫描;
-
汇总排序=内存爆炸,性能炸裂。
**-**04-
三种高能方案破解“深度分页”
方案一:禁止跳页(Feed流常规操作)
核心思路:每次都从上一页最后一条数据继续往下查,不许跳页。
第一次翻页:
SELECT * FROM orders WHERE user_id = 123 ORDER BY create_time DESC LIMIT 10;
下一页查询:
SELECT * FROM orders WHERE user_id = 123 AND create_time < '2023-10-01 12:00:00' ORDER BY create_time DESC LIMIT 10;
✅ 优点:超快!每页只查索引页,0回表,不卡。
❌ 缺点:用户不能跳到第100页,适用于朋友圈、抖音等连续流场景。
方案二:二次查询法(后台系统最优解)
核心思路:拆解大分页 → 先粗查 → 再精准定位。
第一步:各分片并行查询偏移量
-- 每个分片查 offset/分片数 + limit 条数据SELECT create_time FROM orders ORDER BY create_time DESC LIMIT 166666, 10;
得到目标区间的最小时间戳,比如 2023-09-20 08:00:00
第二步:用这个时间戳精确获取最终数据
SELECT * FROM orders WHERE create_time >= '2023-09-20 08:00:00' ORDER BY create_time DESC LIMIT 10;
✅ 优点:支持跳页,性能远超传统深分页。
❌ 缺点:需要两次查询 + 聚合处理逻辑,稍微麻烦些。
方案三:ES + HBase 分而治之(高并发场景的终极解)
核心思路:搜索交给ES,存储交给HBase,强强联手。
流程拆解:
-
写入阶段:订单数据同步写入 MySQL、ES、HBase;
-
查询阶段:
-
第一步:用 ES 快速分页查询 ID;
-
第二步:根据 ID 批量从 HBase 获取详情。
GET /orders/_search
{
"query": { "match_all": {} },
"sort": [{"create_time": "desc"}],
"from": 1000000,
"size": 10
}
✅ 优点:
-
支持任意跳页,百万级数据,响应只需毫秒级;
-
支持复杂搜索条件,扩展性强;
-
无需回表,全靠 ES 倒排索引。
❌ 缺点:
-
架构复杂,ES+HBase 成本高;
-
数据一致性需要用 Binlog 做补偿同步。
**-**05-
面试怎么答,才能拿下 offer?
面试官想听你这样说:
“分库分表后的深度分页,核心问题是全局排序与数据分散,我们有三种主流方案:
禁止跳页:适用于 Feed 流场景,翻页快速无负担;
二次查询法:适用于后台系统,支持跳页,性能优异;
ES+HBase:适用于亿级数据,毫秒响应,搜索友好。
实际项目中,我们曾在订单系统中落地过第二种方案,将分页响应时间从 15 秒降低到 300 毫秒,效果显著。”
想加分?来点骚操作:
-
画个【分库分表 + ES/HBase】架构图,展示全流程;
-
顺带扔出一句:“我们用 Canal 监听 Binlog 实现数据同步”;
-
给个性能对比:“原来查 10 秒,现在查 50 毫秒”。
面试官听到这,直接 P7 起步!
总结:
分页这事,看似简单,实则很有门道。
-
千万数据,不要用 OFFSET 跳页,纯纯自杀行为。
-
分库分表后深度分页,是一场分布式全局排序战争。
-
不同业务场景,要选不同方案,没有银弹,只有匹配。
热门文章
**-**06-
粉丝福利
点点关注,送你互联网大厂面试题库,如果你正在找工作,又或者刚准备换工作。可以仔细阅读一下,或许对你有所帮助!