orderbydesc 每列方向独立、NULL 默认位置与降序索引怎么匹配
ORDER BY ... DESC 看着简单,但"DESC 到底怎么排""NULL 排前面还是后面""为什么加唯一索引的查询还要 filesort"这三个问题,是排序相关慢查询与线上数据错乱的常见来源。本文依据 PostgreSQL 官方文档的排序章节原文整理,SQL Server / MySQL 在默认 NULL 行为上与此不同,文中会单独标注。

一、方向关键字与等值比较符的区别
PostgreSQL 官方排序文档(实测 200)给出的语法是 ASC | DESC,原文说明用这两个"keyword to set the sort direction to ascending or descending",且 "ascending order is the default. Ascending order puts smaller values first"。也就是说 ORDER BY x 与 ORDER BY x ASC 等价,想倒序必须显式写 DESC。
同页还提到排序可由 < 运算符决定,DESC 反之。文档列出的多列写法有三种完全等价的形式:ORDER BY x, y DESC、ORDER BY x ASC, y DESC、ORDER BY x DESC, y DESC。这里有个高频误解:每列的方向独立生效,不继承。第一种写法里 x 是默认升序、只有 y 是降序,想要两列都降序必须写成第三种。
二、NULL 排在哪:默认是"最大",但各家不同
官方文档的原文非常明确:"By default, null values sort as if larger than any non-null value; that is, NULLS FIRST will be equivalent to ASC default, and NULLS LAST will be equivalent to DESC default." 可显式指定 NULLS FIRST 与 NULLS LAST 来改变位置。
但必须知道数据库差异:MySQL 把 NULL 当作最小值排在最前(升序时 NULL 在第一行),SQL Server 同样把 NULL 视为最小。所以同一条 ORDER BY score DESC,在 PostgreSQL 里 NULL 排最前(因为被视为最大),在 MySQL 里 NULL 排最后。跨库迁移或写通用分页代码时,这是典型的"结果顺序莫名变化"的来源。稳妥做法是永远显式写 NULLS FIRST/LAST,或对可空列用 COALESCE 给边界值。
三、排序三型:ordinal、expression、collation
SELECT 参考页(实测 200)的语法框架写作 ORDER BY expression [ ASC | DESC | USING operator ],并说明输出列"can also be referenced (by name or ordinal number) in the ORDER BY clause",即可以按列序号排序(如 ORDER BY 2)。三种常见写法各有陷阱:
- 序号:
ORDER BY 2 DESC指第二个输出列。可读性差,且SELECT列表一调整就错位,维护风险高。 - 表达式:
ORDER BY (price / qty) DESC合法,但这种表达式无法直接命中列索引,往往触发额外排序。 - 排序规则:文本排序依赖列 collation。大小写、重音、中文按拼音还是按字节,全由它决定,出现"排序结果和语言习惯不符"时先查 collation 而不是查代码。
四、为什么有索引还排序
倒序扫描是索引的天然能力:B-tree 从右向左遍历即得 DESC,前提是索引列的顺序与方向完全匹配。以下情况仍要排序:索引是 (a,b) 而查询 ORDER BY a DESC, b ASC(混合方向,需专用降序索引);排序列不在索引中;用了表达式排序;LIMIT 后又需回表。定位方法是看执行计划:出现 Sort/filesort 说明额外排序,Order By 行若显示 "Using index" 通常可省。
优化思路是把方向统一:能用 ORDER BY a DESC, b DESC 就别写成混合方向;分页要稳定顺序时,把唯一列追加到排序末尾,如 ORDER BY created_at DESC, id DESC,避免相同时间戳导致翻页重复或漏数据。
五、LIMIT 与排序的关系
"取最新 10 条"这类需求,ORDER BY ts DESC LIMIT 10 是最直接的 Top-N。注意没有排序的 LIMIT 结果顺序是未定义的,靠它做分页等于随机。带 OFFSET 时数据库仍需扫描并丢弃前 N 行,深分页代价高,改用"上次排序值"作为游标(keyset 分页)能保持恒定成本。
六、常见问题 FAQ
问:ORDER BY 数字到底指什么?
答:指输出列的位置,不是列名也不是值,写序号风险高。
问:DESC 会走索引吗?
答:会,索引支持反向扫描;混合方向才需要降序索引。
问:怎么让 NULL 一定排在最后?
答:跨库统一写法是 ORDER BY (col IS NULL), col DESC 或显式 NULLS LAST(MySQL 不支持该子句,需用前一种技巧)。
七、总结
关于 DESC 排序的可靠认知是:ASC 是默认方向、每列方向独立不继承;PostgreSQL 默认把 NULL 视为最大值,MySQL 与 SQL Server 视为最小值,跨库必须显式处理;可用列序号、表达式与依赖 collation 的文本排序,序号写法最易埋雷;统一方向的排序才能命中索引反向扫描,混合方向需降序索引;分页要追加唯一列保证稳定顺序,深分页改用 keyset 游标。排序结果与预期不符时,先看 NULL 语义与每列方向,再看索引是否匹配,最后才怀疑优化器。
生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »
