某个深夜,电商后台突然响起急促的报警声。运营同事反馈订单查询页面卡死在第83页,5万商家即将开始早高峰接单。我盯着屏幕上的SQL语句,冷汗顺着后背流下:
SELECT * FROM orders ORDER BY create_time DESC LIMIT 1000000, 10;
这就是典型的OFFSET分页陷阱。当数据量突破百万时,这样的查询会让数据库经历一场灾难…
一、为什么传统分页会变慢?
想象一下你在图书馆找一本指定的书:
- 使用
LIMIT 100000,10
就像让管理员先数10万本书 - 数到第100000本时,才拿出后面的10本
- 每次翻页都要重新数一遍所有书
MySQL处理OFFSET时正是如此!它会完整扫描前100000+10条数据,再丢弃前10万条。执行计划中的"Using filesort"就是罪证:
EXPLAIN SELECT * FROM orders ORDER BY create_time DESC LIMIT 1000000,10;
-- 结果
type: ALL
Extra: Using filesort
二、三大优化方案实战
方案一:游标分页法(推荐指数:★★★★★)
像翻书一样记住上次看到的位置:
-- 第一页
SELECT * FROM orders
WHERE create_time <= NOW()
ORDER BY create_time DESC LIMIT 10;
-- 下一页
SELECT * FROM orders
WHERE create_time < '上一页最后的时间'
ORDER BY create_time DESC LIMIT 10;
核心原理:
- 始终只取当前时间点之后的数据
- 需要配合复合索引
(create_time, id)
- 适合无限滚动场景
性能对比:
数据量 | 传统分页 | 游标分页 |
---|---|---|
100万 | 1.2s | 0.02s |
500万 | 6.8s | 0.03s |
方案二:子查询爆破法(推荐指数:★★★★☆)
先查ID再拿数据,像特警队精准突击:
SELECT * FROM orders
WHERE id IN (
SELECT id FROM orders
ORDER BY create_time DESC
LIMIT 1000000,10
);
优化原理:
扫描二维码关注公众号,回复:
17634381 查看本文章

- 子查询先通过覆盖索引快速定位ID
- 外层查询用主键精准抓取数据
- 需要保证id是主键
执行计划对比:
-- 原始查询
type: ALL
rows: 1000010
-- 优化后
type: index
rows: 10
方案三:业务降维打击(推荐指数:★★★☆☆)
当技术优化到极限时,就要从业务角度破局:
- 禁止任意页跳转,改为【上一页/下一页】
- 展示近似总数:“大约100万+订单” 代替精确值
- 热门数据走缓存:将前100页存入Redis
某电商平台优化效果:
优化前平均响应时间:2.1s
优化后平均响应时间:0.15s
服务器CPU消耗下降40%
三、防坑指南
- 索引陷阱:排序字段必须出现在索引最左侧
- 数据空洞:WHERE条件过滤后可能导致分页错乱
- 深度分页:超过1000页建议改用搜索引擎
-- 错误示例:索引未覆盖排序字段
ALTER TABLE orders ADD INDEX idx_user (user_id);
-- 正确姿势:联合索引
ALTER TABLE orders ADD INDEX idx_time_user (create_time, user_id);
四、总结
三种方案的抉择之道:
方案 | 适用场景 | 缺点 |
---|---|---|
游标分页 | 连续翻页 | 不能跳页 |
子查询法 | 需要跳页 | 需要主键 |
业务优化 | 高并发场景 | 功能受限 |
凌晨4点,当我将游标分页+子查询的方案上线后,后台查询响应时间从4.3秒直降到0.08秒。看着监控图上平稳的曲线,我知道这个月的绩效稳了——但更开心的是,5万商家再也不会因为系统卡顿而手忙脚乱了。
最后送大家一句话:优化永无止境,但解决问题的快感,正是我们程序员最好的兴奋剂!