您现在的位置: 365建站网 > 365文章 > SQL 中 ORDER BY DESC 用法详解:多字段排序、索引与分页稳定性

SQL 中 ORDER BY DESC 用法详解:多字段排序、索引与分页稳定性

文章来源:365jz.com 点击数:13 更新时间:2024-02-23 13:41

orderbydesc 实际上是 SQL 的 ORDER BY ... DESC 三段连写。它决定查询结果的排序方式,但很多人踩在两个地方:多字段排序的优先级和分页时排序不稳定导致数据重复/丢失。本文把语法、索引利用与分页稳定性讲透。

sql中order by desc用法与降序排列详解

一、基础语法

-- 单字段降序
SELECT id, name, created_at FROM articles ORDER BY created_at DESC;

-- 升序(ASC 可省略)
SELECT * FROM users ORDER BY score ASC;

-- 多字段:先按 a 降序,a 相同再按 b 升序
SELECT * FROM orders ORDER BY status DESC, pay_time DESC, id ASC;

-- 用表达式/别名排序
SELECT name, LENGTH(name) AS len FROM t ORDER BY len DESC;

-- NULL 的位置
SELECT * FROM t ORDER BY score DESC NULLS LAST;   -- PostgreSQL/Oracle 写法

要点:排序作用于结果集而不是某张表;ORDER BY 必须写在 WHERE/GROUP BY/HAVING 之后、LIMIT 之前;别名可以在 ORDER BY 中使用(MySQL/PostgreSQL 允许,标准 SQL 也允许在 ORDER BY 用 SELECT 别名)。

NULL 处理差异很大:MySQL 升序时 NULL 在最前、降序在最后;PostgreSQL 默认相反(ASC 时 NULL 最后、DESC 时最前),可用 NULLS FIRST/LAST 显式指定。

二、排序不稳定:分页翻出重复数据的根因

这是最容易线上出问题的一条:

-- 有并列值,翻页时顺序可能变
SELECT * FROM products ORDER BY sales DESC LIMIT 10 OFFSET 20;

当 sales 有大量并列值时,数据库不保证相同值的行顺序固定,第 2 页与第 3 页可能出现同一行重复出现或某行始终不出现。解决:始终追加唯一列做 tiebreaker。

SELECT * FROM products ORDER BY sales DESC, id DESC LIMIT 10 OFFSET 20;

深分页性能问题的更好解法是“键集分页”(seek method),避免大 OFFSET 扫描:

SELECT * FROM products
WHERE (sales, id) < (:lastSales, :lastId)
ORDER BY sales DESC, id DESC
LIMIT 10;

(不支持元组比较的数据库改写成 sales < :lastSales OR (sales = :lastSales AND id < :lastId)。)

三、索引:让排序不走 filesort

MySQL 中 EXPLAIN 里 Extra 出现 Using filesort 不代表用了磁盘文件,而是需要额外排序步骤;能命中索引顺序时则不会出现。设计原则:

  1. 索引列顺序与 ORDER BY 完全一致才能免排序;单列索引顺序无所谓(InnoDB 可反向扫描),但混排(ORDER BY a ASC, b DESC)需要 (a ASC, b DESC) 这样的降序索引(MySQL 8.0+ 支持)。
  2. WHERE 用了范围条件,后续排序列就用不上索引顺序。例如 WHERE created_at > ? ORDER BY id 只能排序前先把范围结果取出再排。
  3. 覆盖索引能顺带避免回表:SELECT id, sales FROM products ORDER BY sales DESC LIMIT 10 若建 (sales, id) 索引,直接走索引。
  4. ORDER BY RAND() 会为每行生成随机值并全表排序,大表禁用;改用随机 id 区间或预取候选集。
  5. 字符串排序看排序规则(collation):MySQL 的 utf8mb4_general_ci 不区分大小写,需大小写敏感时用 utf8mb4_bin 或 BINARY;这也是中文排序按拼音还是按笔画差异的来源。

四、分组后排序(ORDER BY 与 GROUP BY 的搭配)

-- 每个分类下取销量最高的 3 篇(MySQL 8.0+ 窗口函数)
SELECT * FROM (
  SELECT a.*, ROW_NUMBER() OVER (PARTITION BY cat_id ORDER BY sales DESC) AS rn
  FROM articles a
) t WHERE t.rn <= 3 ORDER BY cat_id, rn;

老版本 MySQL 没有窗口函数时用变量或关联子查询实现,但性能与可读性都差,建议升级。另外在 ONLY_FULL_GROUP_BY 模式下,SELECT 未分组的列会直接报错,别指望“随便写也能跑”。

五、常见问题 FAQ

问:DESC 是“倒序”还是“倒着读索引”?
答:语义是降序排列。InnoDB 对单列升序索引可以反向扫描(Backward index scan),成本与正扫相近;但多列混排就要真实建降序索引。

问:ORDER BY 数字列为什么 10 排在 2 前面?
答:说明这列是字符串类型(VARCHAR),按字符逐位比较。ORDER BY CAST(col AS UNSIGNED) 是权宜之计,正解是把列改成整数类型。

问:排序字段有 NULL 想放最后怎么办?
答:MySQL 可用 ORDER BY col IS NULL, col DESC;PostgreSQL/Oracle 用 NULLS LAST。

记住三条:①分页排序必带唯一列做 tiebreaker;②深分页改用键集分页;③想让排序命中索引,索引列顺序要与 ORDER BY 完全一致且 WHERE 不含前置范围条件。这三条能解决 90% 的“排序又慢又不准”问题。

生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »