MySQL里order by到底咋排的,顺便聊聊怎么能快点儿
- 问答
- 2025-12-24 09:31:26
- 4
MySQL里ORDER BY到底咋排的?
当你用SELECT * FROM table ORDER BY column的时候,MySQL心里可能在想几种干活的方案,它具体选哪种,得看情况,咱们一个一个说。
第一种,也是最“笨”的一种:全表扫描,在内存里排序。
(来源:MySQL官方文档关于“ORDER BY Optimization”的说明,以及《高性能MySQL》一书中的相关章节)
MySQL会先根据你的WHERE条件(如果没WHERE就是全表)把符合条件的数据行都捞出来,它为每一行数据分配一块小小的内存(叫做“排序缓冲区”,sort buffer),把需要排序的字段(比如你写的column)以及SELECT需要返回的字段都塞进去。
它就在这个内存区域里,对这些行按照ORDER BY的字段进行排序,如果数据量不大,内存完全够用,那排序就很快,用的是快速排序之类的算法,排好序后,直接把结果返回给你。
但问题来了,如果要排序的数据量太大,内存里(sort buffer)放不下了怎么办?MySQL就得用上硬盘了。
(来源:《高性能MySQL》中对于“文件排序”的详细解释)
这时候就进入了“文件排序”模式,它会把数据分成好几批,先在内存里把每一批排好序,然后把每一批排好序的结果当成一个临时文件,写在硬盘上,MySQL再把这些已经部分有序的临时文件合并起来,得到一个最终的有序结果,这个“拆分-排序-合并”的过程,显然比纯内存排序要慢得多,因为涉及到耗时的硬盘读写操作。
第二种,聪明一点的办法:利用索引来排序。
(来源:MySQL官方文档关于“Use of Indexes”的部分,以及多本数据库优化书籍的核心观点)
如果ORDER BY后面跟着的字段,正好有一个合适的索引(你按create_time排序,而create_time字段上刚好有个索引),而且你的查询条件也能利用上这个索引,那MySQL可能就会选择走索引。

为啥这样快呢?因为索引本身就是一个按照这个字段排好序的数据结构(比如B+树),这时候,MySQL可以不用做上面说的那种“全表扫描再排序”的苦力活了,它只需要沿着索引的叶子节点,按顺序读下去,读出来的数据天然就是有序的!它只需要按顺序取出数据,然后返回给你就行了,这样就完全避免了在内存或硬盘上进行排序这个昂贵的操作。
利用索引排序有个前提:索引的顺序必须和ORDER BY的顺序完全一致,比如你ORDER BY create_time DESC,但索引是ASC升序的,那可能就用不上索引排序了,或者用起来效率不高,如果是ORDER BY a, b(按两个字段排序),你最好有一个联合索引(a, b),这样才能高效。
一个常见的坑:不是所有用上索引的查询都能避免排序。
(来源:数据库领域常见的“索引覆盖”优化技巧)
举个例子,你有一个联合索引(category, price),你的查询是:
SELECT * FROM products WHERE category='电子产品' ORDER BY price;
这时候,MySQL能用索引找到所有“电子产品”,但因为你的SELECT *要返回所有字段,而索引里只记录了category、price和主键ID,MySQL需要根据索引找到主键ID后,再回到主键索引(数据文件)里去把整行数据“捞”出来,这个“回表”操作可能不是按照price的顺序进行的,所以MySQL可能最后还是需要把捞出来的这批数据,再在内存里按照price重新排一次序。

那怎么才能让ORDER BY快点儿呢?
明白了上面的原理,优化思路就清晰了,核心就是:尽一切可能避免那个昂贵的“文件排序”,最好是连内存排序都避免,直接走索引按顺序读数据。
-
为排序字段创建索引,这是最有效的一招。
- 如果总是按某个单字段排序,就给它建个单列索引。
- 如果经常按多个字段组合排序(比如
ORDER BY last_name, first_name),那就建一个联合索引(last_name, first_name),顺序要对上。 - 如果排序顺序是
DESC降序,在MySQL 8.0及以上版本,可以创建降序索引(如(last_name DESC, first_name DESC))来获得最佳性能,老版本可能效果不佳。
-
利用“索引覆盖”来避免回表,从而避免排序。
- 接上面的例子,如果我们把查询改成:
SELECT id, category, price FROM products WHERE category='电子产品' ORDER BY price; - 这时,要查询的
id, category, price三个字段,在(category, price)这个联合索引里全都有了!MySQL只需要扫描这个索引,就能拿到所有需要的数据,而且扫描索引的顺序就是按price排好序的,它既不用回表,也完全不用再排序,速度极快,这叫做“覆盖索引”。
- 接上面的例子,如果我们把查询改成:
-
扩大排序缓冲区(sort buffer)。
- 如果实在无法避免文件排序(比如排序字段多变,没法都建索引),可以尝试在MySQL配置里调大
sort_buffer_size这个参数,这样,更多数据可以在内存里完成排序,减少甚至避免向硬盘写临时文件,也能提升速度,但这属于“治标不治本”的系统级调优,而且内存分配过大也可能有副作用,需要谨慎。
- 如果实在无法避免文件排序(比如排序字段多变,没法都建索引),可以尝试在MySQL配置里调大
-
减少不必要的查询字段。
- 别总是
SELECT *,只取出你真正需要的字段,这样有两个好处:一是如果运气好,可能就实现了“索引覆盖”;二是即使覆盖不了,要排序的数据量(行宽度)也变小了,sort buffer能放下的行数就变多了,排序自然更快。
- 别总是
-
优化
WHERE条件,让它也能用上索引。- 有时候
ORDER BY本身能用索引,但WHERE条件太复杂,导致整体查询效率低下,确保WHERE条件中的字段也有合适的索引,让MySQL能快速定位到需要排序的那部分数据,减少需要处理的数据量。
- 有时候
MySQL排序,核心区别就是“文件排序”(慢)和“索引排序”(快),你的优化目标就是让MySQL走上索引排序这条康庄大道,最关键的就是创建正确的索引,并编写能利用覆盖索引的查询,理解了它背后是咋干的,你就能有的放矢,知道劲儿该往哪儿使了。
本文由符海莹于2025-12-24发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/67468.html
