在 Oracle 数据库中,ORDER BY 子句负责对查询结果进行排序。它的执行阶段位于 SELECT、WHERE、GROUP BY 之后,属于最后呈现前的整理操作。很多人把 ORDER BY 理解成简单的升序或降序排列,但在实际业务里,NULL 值怎么放、多列排序的优先级怎样定、排序会不会拖慢查询,这些问题经常被忽略。理解 ORDER BY 的底层行为,能减少分页错乱、结果不一致和性能突然下降的情况。

一、ORDER BY 基础语法与排序规则
先看最基本的写法。ORDER BY 可以放在 SELECT 语句的末尾,后面跟列名、列位置、表达式或列别名。默认按升序排列,如果要降序需要显式加上 DESC。常见的语法如下:
SELECT employee_id, first_name, salary FROM employees WHERE department_id = 50 ORDER BY salary DESC;
这段查询会返回部门 50 的所有员工,并按工资从高到低排序。如果去掉 DESC,结果就按工资从低到高排列。Oracle 还支持使用列位置排序,例如 ORDER BY 3 表示按 SELECT 列表中的第三列排序。这种写法在临时查询里比较方便,但不建议用在生产代码中,因为只要 SELECT 列表调整了列的顺序,排序目标就会跟着变,导致结果难以维护。
表达式排序同样常用。假设需要按员工的综合收入排序,综合收入等于工资加奖金,可以这样写:
SELECT employee_id, first_name, salary, commission_pct FROM employees WHERE department_id = 80 ORDER BY salary + salary * NVL(commission_pct, 0) DESC;
这时 Oracle 会先计算每一行的表达式值,再根据该值排序。类似的,也可以在 ORDER BY 中直接引用 SELECT 列表里定义的列别名。例如把上面的表达式命名为 total_comp,然后使用 ORDER BY total_comp,可读性会更好。需要注意的是,ORDER BY 中使用列别名是 Oracle 的一个便利特性,但别名不能用在 WHERE 子句中,因为 WHERE 在 SELECT 之前解析,而 ORDER BY 在 SELECT 之后解析。
二、NULL 值处理与多列排序技巧
Oracle 在排序时对 NULL 有默认处理规则:升序排序时 NULL 值默认排在最后,降序排序时 NULL 值默认排在最前。这一点和 SQL Server 等数据库的行为不同,也是很多人在跨数据库迁移时踩坑的地方。举个例子,假设 employees 表中有些行的 commission_pct 为 NULL,执行下面语句:
SELECT employee_id, first_name, commission_pct FROM employees WHERE department_id = 80 ORDER BY commission_pct;
因为默认是升序,所有 commission_pct 为 NULL 的行会集中出现在结果集的末尾。如果希望 NULL 排在最前,可以使用 NULLS FIRST;如果希望降序时 NULL 排在最后,可以使用 NULLS LAST。例如:
SELECT employee_id, first_name, commission_pct FROM employees WHERE department_id = 80 ORDER BY commission_pct NULLS FIRST;
多列排序时,Oracle 会按照 ORDER BY 中列出现的先后顺序逐层排序。先按第一列排序,第一列值相同的行再按第二列排序,以此类推。典型场景是部门内员工按工资降序排列:
SELECT department_id, employee_id, first_name, salary FROM employees ORDER BY department_id ASC, salary DESC;
这条语句先按部门升序分组展示,同一部门内部再按工资从高到低排序。多列排序时每一列都可以单独指定 ASC 或 DESC,也可以单独指定 NULLS FIRST 或 NULLS LAST,这给复杂报表提供了很大灵活性。
三、ORDER BY 与索引、性能及常见问题
很多人认为只要排序列上有索引,ORDER BY 就一定很快,其实不一定。Oracle 只有在一定条件下才能利用索引避免排序。比如查询走的是索引范围扫描或全索引扫描,并且索引列的顺序和 ORDER BY 完全一致,同时 ASC/DESC 也匹配,优化器才可能直接按索引顺序返回结果,跳过 SORT ORDER BY 操作。如果查询还要回表取大量列,或者存在额外的过滤条件,优化器可能仍然选择全表扫描加显式排序。
可以通过执行计划查看是否出现 SORT ORDER BY。一般小数据量下排序对性能影响不大,但数据量上百万行后,排序区 PGA 内存不够时会溢出到磁盘临时表空间,查询时间会明显增加。此时可以考虑建立与 ORDER BY 匹配的复合索引,或者调整 PGA 工作区大小。另一方面,分页查询中必须使用 ORDER BY 来保证每页数据顺序稳定。比如下面这种分页写法,如果不加 ORDER BY,两次取第一页可能返回不同顺序:
SELECT *
FROM (
SELECT t.*, ROWNUM rn
FROM (
SELECT employee_id, first_name, salary
FROM employees
ORDER BY salary DESC
) t
WHERE ROWNUM <= 20
)
WHERE rn > 10;
此外,ORDER BY 不能直接作用于 UNION 合并后的整体查询,除非把 UNION 查询包成子查询再排序。像 SELECT ... UNION SELECT ... ORDER BY 1 的写法会让人误以为是对整个结果集排序,但在 Oracle 中 ORDER BY 只作用于最后一个 SELECT 分支,或者不同版本会有语法限制。正确做法是外层再套一层查询。另一个常见问题是列位置排序可读性差,建议在正式代码中始终使用明确的列名或别名。
四、常见问题解答汇总
问:ORDER BY 能否使用 SELECT 列表中没有的列?普通查询中可以,但在使用 DISTINCT 去重时,ORDER BY 中的列必须出现在 SELECT 列表中,否则会报错。原因是 DISTINCT 先对结果集去重,排序阶段无法引用已经被去重逻辑隐藏的列。
问:ORDER BY 和 GROUP BY 谁先执行?GROUP BY 先生效,完成分组和聚合计算,ORDER BY 在最后对分组后的结果进行排序。因此 ORDER BY 中可以使用聚合函数,例如 ORDER BY COUNT(*),但不能在 WHERE 中对聚合函数做过滤。
问:为什么排序列加了索引,执行计划里仍然出现 SORT ORDER BY?通常是因为索引列顺序与 ORDER BY 不一致,或者查询包含额外的排序列但索引没有覆盖,又或者排序方向与索引默认方向相反。此时需要结合 WHERE 条件和 SELECT 列检查是否真正满足了索引有序扫描的条件。

