首页 / MySQL 入门教程 / 排序与分页 ORDER BY 与 LIMIT

MySQL 入门教程

排序与分页 ORDER BY 与 LIMIT

本教程共 46 篇 · 第 22 篇 · 更新于 2026-07-30 · 约 10 分钟阅读

MySQLMySQL 入门教程ORDER BYLIMIT排序分页深分页SQL 优化

22. 排序与分页 ORDER BY 与 LIMIT

本节目标:学会用 ORDER BY 控制查询结果的排序顺序(升序、降序、多列排序),用 LIMIT 实现分页查询,理解深分页的性能问题及优化思路。

22.1 ORDER BY:排序

查询出来的数据默认是无序的。如果你希望按某个列排序,用 ORDER BY

SELECT username, age FROM users
ORDER BY age;

默认是升序(从小到大),等价于显式写 ASC

SELECT username, age FROM users
ORDER BY age ASC;

想从大到小排,用 DESC(降序):

SELECT username, age FROM users
ORDER BY age DESC;
Note

不加 ORDER BY 的查询结果不保证顺序。你可能发现多次查询结果”碰巧”顺序一样,但那只是巧合,MySQL 没有任何排序保证。需要有序结果时,必须写 ORDER BY。

22.2 多列排序

按多个列排序时,用逗号分隔,排序有优先级:

SELECT username, age, created_at FROM users
ORDER BY age DESC, created_at ASC;

先按 age 降序排,age 相同的再按 created_at 升序排。

每个列可以单独指定升序或降序:

-- 年龄从大到小,同龄的按注册时间从早到晚
SELECT username, age, created_at FROM users
ORDER BY age DESC, created_at ASC;
Tip

排序列的顺序很重要。ORDER BY age DESC, created_at ASCORDER BY created_at ASC, age DESC 结果完全不同。排在前面的列优先级更高,先按它排,排不出来(值相同)的才看后面的列。

22.3 按别名和表达式排序

ORDER BY 在 SELECT 之后执行(参考第 19 章的执行顺序),所以可以用列别名和表达式:

-- 用别名排序
SELECT username, age * 365 AS 出生天数
FROM users
ORDER BY 出生天数 DESC;

-- 用表达式排序
SELECT username, age
FROM users
ORDER BY age * -1;
-- 等价于 ORDER BY age DESC,但可读性差

也可以用列的序号位置排序:

-- 按第二列排序
SELECT username, age FROM users
ORDER BY 2;
-- 等价于 ORDER BY age
Warning

用列序号排序(ORDER BY 2)不推荐。一旦改了 SELECT 的列顺序,排序就错了。而且可读性差,谁知道 2 是哪个列?老老实实写列名或别名。

22.4 NULL 在排序中的位置

MySQL 默认把 NULL 排在最前面(升序时)或最后面(降序时):

-- 升序:NULL 排最前
SELECT username, age FROM users ORDER BY age ASC;
-- NULL, 20, 25, 30, ...

-- 降序:NULL 排最后
SELECT username, age FROM users ORDER BY age DESC;
-- 30, 25, 20, ..., NULL
Note

这跟其他数据库不一样。Oracle 默认升序时 NULL 排最后,PostgreSQL 也是。如果你从别的数据库迁移过来,注意这个差异。MySQL 8.0.13+ 支持原生 NULLS FIRST / NULLS LAST 语法控制 NULL 位置。26.7 基线下可直接使用。如果要在更早版本(8.0.13 之前)实现相同效果,需要通过 CASE 表达式实现跨数据库兼容。

22.5 LIMIT:限制返回行数

查出来太多行,只想要前几条?用 LIMIT

-- 只返回前 5 条
SELECT * FROM users
ORDER BY age DESC
LIMIT 5;

LIMIT 通常跟 ORDER BY 配合使用。不排序的话,LIMIT 返回的是”随便”的 5 条,没有实际意义。

LIMIT 偏移分页

分页查询是 LIMIT 最常见的用法。语法是 LIMIT 偏移量, 行数

-- 第 1 页(偏移 0,取 10 条)
SELECT * FROM users
ORDER BY id ASC
LIMIT 0, 10;

-- 第 2 页(偏移 10,取 10 条)
SELECT * FROM users
ORDER BY id ASC
LIMIT 10, 10;

-- 第 3 页(偏移 20,取 10 条)
SELECT * FROM users
ORDER BY id ASC
LIMIT 20, 10;

偏移量的计算公式:偏移量 = (页码 - 1) * 每页行数

还有一种写法用 OFFSET 关键字,效果一样但可读性更好:

SELECT * FROM users
ORDER BY id ASC
LIMIT 10 OFFSET 20;

先写行数再写偏移量,跟 LIMIT 20, 10 是同一个意思。

Tip

LIMIT offset, count 这种写法容易搞混哪个是偏移哪个是数量。推荐用 LIMIT count OFFSET offset 写法,语义更清晰。

22.6 深分页性能问题

分页查到后面几页时,性能会急剧下降。这叫”深分页”问题。

-- 第 10000 页,每页 10 条
SELECT * FROM orders
ORDER BY id ASC
LIMIT 99990, 10;

这条语句虽然只返回 10 条数据,但 MySQL 实际上要先查出前 99990 条,然后丢掉它们,只返回最后 10 条。数据量越大,翻得越深,越慢。

优化方案一:延迟关联(子查询优化)

利用覆盖索引先查出 id,再关联回原表:

SELECT o.*
FROM orders o
INNER JOIN (
    SELECT id FROM orders
    ORDER BY id ASC
    LIMIT 99990, 10
) AS t ON o.id = t.id;

子查询 SELECT id 只查索引列(如果 id 是主键,走覆盖索引),不回表,速度快。拿到 10 个 id 后再关联回去取完整数据。

优化方案二:记住上一页的最后一条

不使用偏移量,而是用上一页最后一条的 id 作为起点:

-- 假设上一页最后一条 id 是 99990
SELECT * FROM orders
WHERE id > 99990
ORDER BY id ASC
LIMIT 10;

这种方式完全没有偏移开销,无论翻到第几页都一样快。缺点是只能”上一页/下一页”翻,不能直接跳到第 N 页。

优化方案三:限制最大翻页数

产品层面控制,比如最多允许翻 100 页。大多数用户根本不会翻到第 1000 页,深分页很多时候是爬虫在搞事。

Warning

深分页不是 SQL 写法的问题,而是 LIMIT OFFSET 机制本身的局限。理解原理后选合适的优化方案。我之前优化过一个报表系统,把 LIMIT 偏移改成 WHERE id > 上次最大id,查询时间从 3 秒降到 2 毫秒。

22.7 LIMIT 在 UPDATE 和 DELETE 中的妙用

LIMIT 不只能用在 SELECT 里。前面章节提到过,它可以用在 UPDATE 和 DELETE 中限制操作行数:

-- 只更新最早的 100 条 pending 订单
UPDATE orders
SET status = 'expired'
WHERE status = 'pending'
ORDER BY created_at ASC
LIMIT 100;

-- 只删最早的 1000 条过期订单
DELETE FROM orders
WHERE status = 'expired'
ORDER BY created_at ASC
LIMIT 1000;

分批操作大表数据时,这个用法非常实用。

22.8 排序与索引的关系

如果 ORDER BY 的列上有索引,MySQL 可能直接利用索引的有序性,跳过排序步骤(叫”using index”或”filesort”优化)。

-- 如果 age 上有索引,排序可以直接走索引
SELECT * FROM users ORDER BY age;

-- 如果是复合索引 (status, created_at),这个排序也能走索引
SELECT * FROM orders WHERE status = 'paid'
ORDER BY created_at;

但有些情况索引帮不了:

  • ORDER BYWHERE 用了不同的索引列
  • 升降序混合且索引不是按这个方向建的
  • 排序列上有函数或表达式
Note

MySQL 用 filesort(文件排序)来处理无法走索引的排序。数据量大时 filesort 会写临时文件,消耗磁盘 IO。用 EXPLAIN 看到 Extra 列有 Using filesort 就要注意了,索引优化章节会详细讲。

22.9 小结

ORDER BY 控制排序,LIMIT 控制行数,两者配合实现分页。核心记住:排序要写列名不用序号、分页推荐 LIMIT count OFFSET offset 写法、深分页用 WHERE id > 上次最大id 优化、UPDATE/DELETE 也能配合 LIMIT 分批操作。下一章学聚合函数和分组统计。