上周帮一个朋友看代码,他写了个分页查询,排序字段用的是 “id desc”,结果翻到第二页就乱了。他挠着头问我:不是按 id 倒序排好了吗,怎么数据对不上?我看了眼 SQL,order by 后面只跟了一个字段。问题就在这儿——数据库的排序,比大多数人想的要复杂得多。

先说最基础的坑。很多人觉得 order by 就是排个序而已,写起来随意,测试时也没问题。但数据量一上去,排序的代价会直接拉满。MySQL 里,如果 order by 的字段没有索引,它就得把所有数据加载到内存或磁盘临时表里做文件排序,这个过程叫 filesort。你查个几十万行,没索引的排序可能比有索引的慢几十倍。我见过一次线上慢查询,就是因为在 order by 里加了个时间字段,而那个字段没有索引,每次排序都要扫全表,接口响应时间从几十毫秒飙到 3 秒多。后来加了一个联合索引,查询瞬间回到毫秒级。
再说排序的稳定性。SQL 标准里,order by 不保证稳定排序。什么意思?就是如果只按一个字段排序,比如按 “status asc”,status 相同的行,它们的相对顺序是不确定的。不同数据库、不同版本,甚至同一个库的不同执行计划,都可能给出不同的结果。所以分页查询时,如果只按一个非唯一字段排序,翻页就会乱。解决办法很简单:在 order by 里加上主键或唯一字段作为第二排序列。比如 “order by status asc, id asc”,这样 status 相同的行就按 id 排,顺序就固定了。我的朋友就是这样解决的。
还有一个容易被忽略的点:多字段排序的写法和执行顺序。很多人习惯写成 “order by field1, field2 desc”,以为两个字段都是降序。实际上,只有紧跟在 desc 前面的那个字段是降序,前面的字段默认是升序。正确写法应该是 “order by field1 desc, field2 desc”。这个坑在写复杂报表查询时尤其常见,排序列一多,稍微不留神就会写反。我见过一个财务系统,就因为排序方向写反,月度报表里的金额排序整个颠倒,审计差点出问题。
然后是排序字段的类型转换问题。数据库在排序时会隐式转换类型,比如把字符串转成数字,或者把数字转成字符串。这个转换本身就有性能开销,而且转换规则不一定符合预期。举个例子:字段类型是 varchar,但存的是数字,你写 “order by field asc”,排序结果会按字符串字典序排,1 后面跟着 10,而不是 2。要解决这个问题,要么字段类型用对,要么在排序时显式转换,比如 “order by cast(field as unsigned) asc”。我见过有人把订单号存成 varchar,然后按订单号排序,结果 10 号订单排在了 2 号前面,业务方直接炸了。
还有一个高阶用法:用 order by 配合 limit 做分组排序。比如想查每个分类下销量最高的前 3 个商品,传统做法是写子查询或窗口函数。但在不支持窗口函数的低版本 MySQL 里,可以用 order by 加 limit 配合 union 来实现。不过更推荐的做法是直接用窗口函数 ,效率高且可读性强。我最近在一个电商系统里就用了这个写法,从原来需要跑 5 分钟的报表查询,优化到 3 秒内。
说说排序对性能的影响。排序操作本身是 CPU 和内存密集型的,数据量大的时候,排序可能占整个查询时间的 80% 以上。优化思路有几个:一是尽量让排序字段有索引,这样数据库可以直接按索引顺序读取数据,省去排序步骤;二是减少排序的数据量,先用 where 过滤掉大部分数据,再对结果集排序;三是慎用 distinct 和 group by,它们内部也会触发排序,很多时候可以用 exists 或 join 来替代。我优化过最离谱的一个查询,是某系统里 order by 了 20 个字段,而且全是没索引的。砍掉一半字段,给剩下的字段建索引后,查询时间从 15 秒降到 0.2 秒。
数据库的排序看似简单,实则处处是坑。从排序稳定性到类型转换,从索引优化到窗口函数,每一个细节都可能决定系统是快如闪电还是慢如蜗牛。写 SQL 时,多花一分钟想想 order by 怎么用,可能就少花一小时排查线上问题。记住:排序不是简单地把数据排个序,而是让数据库按你的意图高效地组织数据。搞懂了这点,你离资深工程师就更近一步。


