3招让百万级数据分页查询快如闪电(附实战代码)

某个深夜,电商后台突然响起急促的报警声。运营同事反馈订单查询页面卡死在第83页,5万商家即将开始早高峰接单。我盯着屏幕上的SQL语句,冷汗顺着后背流下:

SELECT * FROM orders ORDER BY create_time DESC LIMIT 1000000, 10;

这就是典型的OFFSET分页陷阱。当数据量突破百万时,这样的查询会让数据库经历一场灾难…

一、为什么传统分页会变慢?

想象一下你在图书馆找一本指定的书:

  1. 使用LIMIT 100000,10就像让管理员先数10万本书
  2. 数到第100000本时,才拿出后面的10本
  3. 每次翻页都要重新数一遍所有书

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;

核心原理:

  1. 始终只取当前时间点之后的数据
  2. 需要配合复合索引(create_time, id)
  3. 适合无限滚动场景

性能对比:

数据量 传统分页 游标分页
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 查看本文章
  1. 子查询先通过覆盖索引快速定位ID
  2. 外层查询用主键精准抓取数据
  3. 需要保证id是主键

执行计划对比:

-- 原始查询
type: ALL
rows: 1000010

-- 优化后 
type: index
rows: 10

方案三:业务降维打击(推荐指数:★★★☆☆)

当技术优化到极限时,就要从业务角度破局:

  1. 禁止任意页跳转,改为【上一页/下一页】
  2. 展示近似总数:“大约100万+订单” 代替精确值
  3. 热门数据走缓存:将前100页存入Redis

某电商平台优化效果:

优化前平均响应时间:2.1s
优化后平均响应时间:0.15s
服务器CPU消耗下降40%

三、防坑指南

  1. 索引陷阱:排序字段必须出现在索引最左侧
  2. 数据空洞:WHERE条件过滤后可能导致分页错乱
  3. 深度分页:超过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万商家再也不会因为系统卡顿而手忙脚乱了。

最后送大家一句话:优化永无止境,但解决问题的快感,正是我们程序员最好的兴奋剂!