你写列表接口时,十有八九要排序和分页。核心答案一句话:排序用 ORDER BY 指定字段和方向,分页用 LIMIT 加偏移量取一小段;数据量大、排序分页变慢时,再靠索引和覆盖扫描兜底。但 ORDER BY 和 LIMIT 各自都有坑:排序可能触发 filesort 把内存吃满,分页在大偏移量时会越翻越慢,两件事叠在一起更容易让接口超时。本文以 MySQL 8.0 为例,先分别讲清排序与分页的几种写法,再讲它们叠加时的性能陷阱和优化手段,顺带把游标分页、组合索引这些实用方案也说透。看完你不仅能写出正确的列表查询,还能判断什么时候该加索引、什么时候该换分页策略。今天这篇文章,编程狮就把这块讲透。

先看结论
排序与分页不是两个孤立动作,要先排后取,叠加时性能陷阱最多:
| 你的场景 | 首选写法 | 一句话理由 |
|---|---|---|
| 小数据量、简单列表 | ORDER BY + LIMIT | 语法简单,加好索引就够快 |
| 大偏移量深翻页 | 游标分页或延迟关联 | 避免越翻越慢的偏移扫描 |
| 排序字段无索引或高频变更 | 组合索引覆盖 | 把 filesort 和回表一起干掉 |
一、SQL 排序与分页要解决的两个问题
精确查询 WHERE id = 1 只要定位一行,但列表页的本质是「先看一部分、再按顺序看」。排序解决的是「按什么顺序看」,分页解决的是「一次看多少、从第几条开始看」。两者叠加才是后台列表接口的真正形态,也是 SQL 排序与分页这道基本功的全部内容。
精确等于查不出「前 20 条最新的订单」或「按价格从高到低展示」。这类需求只能交给排序与分页共同完成。要系统学语法,可以先过一遍 SQL 教程,把 SELECT 的基本结构铺平。
⚠️ 注意:排序与分页都依赖「有序」这个前提。如果排序列上没有索引,数据库就得先把结果集全部读出来再排序,数据一大就会非常吃力,这一点在后面的性能陷阱里会重点讲。
二、ORDER BY 排序:方向、多列与性能
ORDER BY 的语法非常直白:跟在字段名后面用 ASC 表示升序,DESC 表示降序;省略时默认升序。最常见的是按时间倒序看最新数据。
-- 按创建时间倒序,最新的 20 条在最前面
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
LIMIT 20;
上面这条做了「先排序、再取前 20 条」。预期结果里第一条是创建时间最大的记录。需要多列排序时,依次列出字段即可,例如先按状态升序、再按时间倒序。
-- 状态在前、时间在后,两列共同决定顺序
SELECT id, status, created_at
FROM orders
ORDER BY status ASC, created_at DESC
LIMIT 20;
性能的关键在排序列有没有索引。有索引时,MySQL 可以顺着 B+ 树的顺序直接取,几乎不额外消耗;没有索引时,会走 filesort,把参与排序的行读进排序缓冲区,内存不够还要落盘,速度断崖式下跌。不同数据库的语法细节可以翻 MySQL 速查手册 查具体用法。

三、LIMIT 分页:偏移量与深翻页陷阱
分页用 LIMIT 实现,它接受两个参数:第一个是「跳过的条数」(偏移量),第二个是「要取的条数」。第一页偏移 0,第二页偏移 20,依此类推。
-- 第 1 页:取前 20 条
SELECT id, title FROM articles ORDER BY id DESC LIMIT 0, 20;
-- 第 3 页:跳过前 40 条,取接下来的 20 条
SELECT id, title FROM articles ORDER BY id DESC LIMIT 40, 20;
写法很简单,但 LIMIT 的偏移量越大越慢,这是分页查询最经典的坑。原因是 LIMIT 40000, 20 并不是「直接跳到第 40001 条」,而是数据库要先读出 40020 条,再丢掉前 40000 条,只返回最后 20 条。偏移量到几十万时,这种浪费会让接口明显变慢。
-- 深翻页:偏移量越大越慢
SELECT id, title FROM articles ORDER BY id DESC LIMIT 40000, 20;
解决深翻页有两个实用思路:一是游标分页,让前端把上一页最后一条的 id 传回来,用 WHERE id < ? 代替大偏移量;二是延迟关联,先只查索引拿 id,再回表取详情,减少回表行数。

四、SQL 排序分页叠加时的性能陷阱
把排序查询和分页查询放在一起,问题会成倍放大。最典型的场景是「按非索引字段排序后再深翻页」:数据库先 filesort 排好序,再跳过几万行取一小段,CPU 和 IO 一起爆。本质上,排序和分页都依赖「顺序读取」,而大偏移量偏偏要打断这种顺序。
| 方案 | 适用场景 | 优点 | 代价 |
|---|---|---|---|
| ORDER BY + LIMIT | 小数据量、浅翻页 | 语法最简单 | 深翻页越翻越慢 |
| 游标分页(WHERE id < ?) | 无限下拉、深翻页 | 不随偏移量变慢 | 不支持随机跳页 |
| 组合索引覆盖 | 排序分页高频且稳定 | 干掉 filesort 和回表 | 索引要针对查询设计 |
踩坑清单
- 现象:列表接口翻到后面越来越卡;原因:LIMIT 偏移量过大导致先读后丢;修复:改用游标分页或延迟关联。
- 现象:排序字段加了索引还是慢;原因:索引顺序与 ORDER BY 不匹配,仍走 filesort;修复:建「WHERE 等值 + ORDER BY 字段」的组合索引。
- 现象:排序结果不稳定、同分记录乱跳;原因:排序列不唯一,每次顺序不确定;修复:追加一个唯一字段(如主键 id)作为兜底排序。
- 现象:排序直接拖垮内存;原因:filesort 缓冲区不够,落到磁盘临时文件;修复:缩小排序行宽或增大排序缓冲区并加索引。
总结
SQL 排序与分页的写法本身很简单:排序靠 ORDER BY 指定字段和方向,分页靠 LIMIT 加偏移量。真正的难点在两者叠加时的性能——小数据量、浅翻页直接写就够,深翻页要换游标分页或延迟关联,高频排序分页则用组合索引把 filesort 和回表一起干掉。记住最痛的一课:LIMIT 的大偏移量不是「跳转」而是「先读后丢」,生产环境一定要规避。
要点带走:
- ORDER BY 决定顺序,LIMIT 决定取哪一段,二者必须配合;
- 排序列没索引会走 filesort,数据一大就吃力;
- 深翻页用游标分页或延迟关联,别硬扛大偏移量。
下一步可以动手给常用列表字段补上组合索引,直观感受排序分页耗时的变化。
动手验证清单
下面这组动作帮你在本机验证三种排序分页写法,按你自己的环境执行,结果以实际输出为准(本文 SQL 未在作者本机执行,仅给出预期形态):
- 安装:本地起一个 MySQL 8.0,或用 Docker 拉取镜像;
- 配置:建一张
articles表,插入几万条带created_at的测试数据; - 命令:执行
ORDER BY created_at DESC LIMIT 0, 20验证浅翻页; - 运行:在命令行或客户端跑该语句,观察返回的前 20 行;
- 检查:用
EXPLAIN查看是否出现Using filesort; - 预期:无索引时 Extra 显示 filesort,加索引后消失;
- 失败:若深翻页
LIMIT 40000, 20明显变慢; - 修复:改用
WHERE id < ?的游标分页重写; - 验证:对比两种写法的执行耗时,确认游标分页更稳。
延伸学习
想把 SQL 检索能力补完整,可以顺着这条线:
- 先过一遍 SQL 入门课程,把基础语法铺平;
- 对比性能看 MySQL 代价估计器怎么用;
- 想优化复杂查询再读 SQL 分层查询怎么写。
常见问题
Q:ORDER BY 和 LIMIT 一起用,顺序有讲究吗?
A:必须先 ORDER BY 后 LIMIT,语义是「先排好序,再取前 N 条」。如果反过来或漏掉排序,取出来的那一段顺序是不确定的,分页结果会错乱。
Q:为什么 LIMIT 偏移量越大查询越慢?
A:LIMIT 40, 20 的真实动作是先把前 60 条读出来再丢掉前 40 条,只返回后 20 条。偏移量越大,被读出来又扔掉的行越多,CPU 和 IO 浪费越严重,所以会越翻越慢。
Q:深翻页除了游标分页还有别的办法吗?
A:有。延迟关联先通过覆盖索引只拿主键 id,再按 id 回表取详情,能显著减少回表行数;另外也可以限制最大翻页深度,超过一定页数就提示用户缩小筛选条件。

TRAE-AI编程



