order by 字段顺序即实际优先级,需严格匹配索引字段顺序、方向及前导条件;多字段排序是逐级筛选而非并行比较;null 排序须显式控制;混合 asc/desc 必须单独声明;动态拼接需字段与方向成对传入。

ORDER BY 字段顺序 = 实际优先级,不能靠“看起来都写了”蒙混
多字段排序不是并行比较,而是逐级筛选:先按第一个字段分大组,值相同时才用第二个字段再分,依此类推。比如 ORDER BY status, created_at DESC 的语义是“先归类为 draft/pending/done,每类内部再取最新一条”;如果写成 ORDER BY created_at DESC, status,结果就是“所有记录按时间倒排,status 只在秒级时间相同时起作用”,业务逻辑直接错位。
常见翻车点:
- 把高区分度字段(如
user_id)放后面,导致前导字段(如is_deleted)只有 2 个值,索引几乎失效 - 前端传来的排序配置没校验顺序,
["created_at", "status"]被直接拼进 SQL,但业务本意是先筛状态再看时间 - 用列序号写法(如
ORDER BY 2, 1),SELECT 一改字段顺序,排序就全乱
混合 ASC/DESC 必须每个字段单独声明,不写 ≠ 继承前一个
ORDER BY name DESC, age 中,name 是降序、age 是升序(ASC 默认,但绝不是“继承”)。很多人误以为逗号后统一生效,结果查出来姓名倒序、年龄却正序,报表对不上。
安全做法:
- 所有字段显式写方向:
ORDER BY name DESC, age DESC - 避免省略:哪怕都想要
ASC,也写全ORDER BY a ASC, b ASC,降低维护成本 - 动态拼接时,字段和方向必须成对传入,不能只传字段名数组,漏掉方向配置
NULL 值排序行为跨数据库不一致,必须显式控制
MySQL 默认 ASC 时 NULL 排最前,PostgreSQL 默认排最后。同一段 SQL 在测试环境(MySQL)跑得对,上线到生产(PostgreSQL)就分页错乱。
兼容性写法(MySQL/PostgreSQL/SQLite 全支持):
- 想让
NULL排前面:ORDER BY (deadline IS NULL), deadline ASC - 想让
NULL排后面:ORDER BY (deadline IS NOT NULL) DESC, deadline ASC - 别依赖
NULLS LAST:MySQL 5.7 不支持,低版本或跨库场景直接报错
复合索引能否跳过 filesort,只看 ORDER BY 是否严格匹配
索引不是“有就行”,而是要和 ORDER BY 的字段顺序、方向、前导条件三者完全对齐。例如查询 SELECT * FROM orders WHERE user_id = 123 ORDER BY status ASC, created_at DESC:
- 理想索引是
(user_id, status, created_at)——WHERE字段必须最左,ORDER BY字段顺序和方向必须完全一致 - 若索引是
(status, created_at),但WHERE没用status,该索引大概率被忽略 - 若索引是
(status ASC, created_at ASC),但ORDER BY要created_at DESC,MySQL 5.7 会触发Using filesort
验证方式:执行 EXPLAIN 看 Extra 列是否含 Using filesort。一旦出现,说明排序没走索引,数据量上万后延迟明显。
最容易被忽略的是语义优先级和物理顺序的对齐——少一个 DESC、错一位字段位置、漏处理 NULL,都可能让 TOP-N 查询拿错数据,而问题在小数据量下根本暴露不出来。










