你还在用LIMIT 1000000,10?献上分页查询优化技巧

0 阅读3分钟

我是小耶,干运营半路出家的野生DBA——写功课只是为了我踩过的坑,你们别再踩了!

刚转行时我写的分页查询是 SELECT * FROM orders ORDER BY id LIMIT 1000000, 10。前100页还好,用户翻到第200页就开始转圈。后来才知道这是典型的“深分页”问题。

先解释一个基础概念:LIMIT M, N ​到底怎么执行? 数据库拿到这个命令后,会老老实实从第一行开始扫描,扫到第 M+N 行,然后扔掉前面的 M 行,只返回最后 N 行。就像你翻一本1000页的书,要读第900页到910页,但你不能直接翻到900页,只能从第一页开始一页一页翻。M越大,翻的页数越多,越慢。

为什么很多人会踩这个坑? 因为小数据量时(比如几千行)感觉不到慢,一旦表增长到百万、千万级,LIMIT 1000000, 10 可能需要扫描上百万行,耗时几秒甚至几十秒。

优化方法一:记住上次位置(游标法) 适用于“下一页”按钮,不需要跳页。假设上一页最后一条记录的 id 是 1000000:

SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;

这就像你合上书,在1000页夹一张书签,下次直接翻到书签位置。数据库利用主键索引快速跳过,只扫描10行,速度恒定在毫秒级。 ​缺点​:不能跳页(比如直接点第1000页),且如果id之间有删除,每页数量可能不均匀,但通常可接受。

优化方法二:子查询先定位起始id(支持跳页) 如果用户非要跳页,可以用子查询先查出第 M 条开始的 id,再取数据:

SELECT * FROM orders 
WHERE id >= (SELECT id FROM orders ORDER BY id LIMIT 1000000, 1) 
ORDER BY id LIMIT 10;

里面的子查询 SELECT id FROM ... LIMIT 1000000,1 只查id列,而id列通常有主键索引,索引体积小,扫描速度快。拿到起始id后,外层 WHERE id>=... 的主键查询也是走索引。 ​实测​:500万数据,传统写法2.3秒,改后0.05秒。

优化方法三:延迟关联(当你要查所有列时特别有效) 如果必须 SELECT * 返回所有字段,可以先查主键,再关联回原表:

SELECT * FROM orders 
JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 10) AS tmp USING(id);

子查询只取id(索引覆盖,极快),然后通过id去原表批量取完整行。比直接 SELECT * 少了很多不必要的数据传输。

什么时候不需要优化? 数据量小(几千行)或用户几乎不走翻页,可以用简单写法。但一旦成为通用查询,建议提前优化。

【这个知识点能帮你什么?】

  • 避免因深分页导致接口超时、数据库CPU飙升。
  • 不用加额外缓存的成本,纯粹靠SQL改写就能大幅提升体验。
  • 面试或技术分享时,这是一个非常经典的优化案例,能展示你对数据库执行细节的理解。

小耶在手,SQL 不愁。

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~