搜索
首页数据库mysql教程你真的了解MySQL的order by吗

本篇文章给大家带来了关于mysql中order by排序的相关知识,希望对大家有帮助。

你真的了解MySQL的order by吗

排序这个词,我的第一感觉是几乎所有App都有排序的地方,淘宝商品有按照购买时间的排序、B站的评论有按照热度排序的...,当然我们今天说的并不是大数据下该如何优雅的排序,如何提升排序性能的问题,我们说一说MySQL中的排序。

对于MySQL,一说到排序,你第一时间想到的是什么?关键字order by?order by的字段最好有索引?叶子结点已经是顺序的?还是说尽量不要在MySQL内部排序?

事情的起因

现在假设有一张用户的朋友表:

CREATE TABLE `user` (
  `id` int(10) AUTO_INCREMENT,
  `user_id` int(10),
  `friend_addr` varchar(1000),
  `friend_name` varchar(100),  
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`)
) ENGINE=InnoDB;

表中目前有两个点需要关注下:

用户的 user_id ,朋友的姓名 friend_name、朋友的地址 friend_addr

user_id 是有索引的

有一天,有个初级开发工程师小猿,收到了来自初级产品经理小汪的需求:

小汪:小猿同志,现在需要在后台加个功能,这个功能要支持根据用户 id 能查到他所有的朋友姓名和地址,并且要求朋友的姓名是按照字典排序的。

小猿:好的,这个功能简单,我马上就上线。

于是小猿书写了这样的sql:

select friend_name,friend_addr from user where user_id=? order by name

在电光石火的瞬间,小猿趾高气昂的上线了,这一切都很顺利,直到有一天有个运营同学导致了这样的查询:

select friend_name,friend_addr from user where user_id=10086 order by name

然而,这个查询竟然比平时慢很多,数据库报了慢查询,小猿此时慌的一b:这是怎么回事?user_id 明明有索引啊,而且机智地我还只用了 select friend_name,friend_addr,并没有用 select *呀。小猿此时不停地安慰自己,要淡定要淡定,然后突然想到有个explain命令,用explain来查看下那条sql的执行计划吧,当小猿用了explain之后,发现extra字段里面有个看起来很危险的字眼:using filesort。

“这个查询竟然用到了传说中的文件排序,但是如果一个人朋友不是很多,就算了用了文件排序,应该也很快吧”,除非这个user_id=10086的朋友很多,后来小猿去查了下,这个用户的朋友竟然有10w多个~。

陷入了沉思的小猿心想:这个锅看来是背定了,10w数据是有点大了,还有这个 using filesort 到底是怎么个排序原理?

解剖文件排序

有人可能说上面的问题是10w数据太大了,就算不排序也慢,这个其实是有道理的,10w数据一次性查出来,无论是MySQL内存缓冲区的占用,还是网络带宽的消耗都是非常大的,那如果我加了limit 1000呢?网络带宽的问题肯定是解决了,因为数据包整体变小了,但是 using filesort 的问题其实还是没有解决,看到这里你可能会有疑问,using filesort 难道是在文件中排序的?在文件中到底是怎么排序的?或者我这样问:如果给你来设计排序你会怎么处理?带着这些疑问和思考我们来看看 using filesort 会涉及到哪些技术难点以及是如何解决的?

  • 首先我们的 user_id 是有索引的,所以会先在 user_id 索引树上检索我们的目标数据,即 user_id=10086 的数据,但是我们要查询的是 friend_name 和 friend_addr 字段,很不幸,光靠 user_id 索引是找不到这两个字段值的

  • 于是需要回表,通过 user_id 对应的主键去主键索引树上去查找,ok,我们找到了第一条 user_id=10086 的 friend_name 和 friend_addr 字段

  • 这时该怎么办?直接返回回去肯定不对,因为我需要对 friend_name 排序,如何排?数据都还没找全,那么就得把查到的数据先放在一个地方,这个地方就是 sort_buffer,看到名字我想你应该猜出来,没错,sort_buffer 就是用于这种情况下排序用的缓冲区,这里需要注意的是每个线程都会有一个单独的 sort_buffer,这么做的目的主要是为了避免多个线程对同一块内存进行操作带来锁竞争的问题。

  • 当第一条数据的 friend_name 和 friend_addr 已经放入 sort_buffer 中,这当然没完,会一直重复同步的步骤,直至把所有 user_id=10086 的 friend_name 和 friend_addr 都放入到 sort_buffer 中才结束

  • sort_buffer 中的数据已经放入完毕,接下来就该排序了,这里 MySQL 会对 friend_name 进行快排,通过快排后,sort_buffer 中 friend_name 就是有序的了

  • 最后返回 sort_buffer 中的前1000条,结束。

21.png

一切看起来很丝滑,但是 sort_buffer 占用的是内存空间,这就尴尬了,内存本身就不是无限大的,它肯定是有上限的,当然 sort_buffer 也不能太小,太小的话,意义不大。在 InnoDB 存储引擎中,这个值是默认是256K。

mysql> show variables  like 'sort_buffer_size';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| sort_buffer_size | 262144 |
+------------------+--------+

也就是说,如果要放进 sort_buffer 中的数据是大于256K的话,那么采用在 sort_buffer 中快排的方式肯定是行不通的,这时候,你可能会问:MySQL难道不能根据数据大小自动扩充吗?额,MySQL是多线程模型,如果每个线程都扩充,那么分给其他功能buffer就小了(比如change buffer等),就会影响其他功能的质量。

这时就得换种方式来排序了,没错,此时就是真正的文件排序了,也就是磁盘的临时文件,MySQL会采用归并排序的思想,把要排序的数据分成若干份,每一份数据在内存中排序后会放入临时文件中,最终对这些已经排序好的临时文件的数据再做一次合并排序就ok了,典型的分而治之原理,它的具体步骤如下:

  • 先将要排序的数据分割,分割成每块数据都可以放到 sort_buffer 中

  • 对每块数据在 sort_buffer 中进行排序,排序好后,写入某个临时文件中

  • 当所有的数据都写入临时文件后,这时对于每个临时文件而言,内部都是有序的,但是它们并不是一个整体,整体还不是有序的,所以接下来就得合并数据了

  • 假设现在存在 tmpX 和 tmpY 两个临时文件,这时会从 tmpX 读取一部分数据进入内存,然后从 tmpY 中读取一部分数据进入内存,这里你可能会好奇为什么是一部分而不是整个或者单个?因为首先磁盘是缓慢的,所以尽量每次多读点数据进入内存,但是不能读太多,因为还有 buffer 空间的限制。

  • 对于 tmpX 假设读进来了的是 tmpX[0-5] ,对于 tmpY 假设读进来了的是 tmpY[0-5],于是只需要这样比较:

如果 tmpX[0] 09c01edb5b4e212d4d32eac23fb0e3e0 tmpY[0],那么 tmpY[0] 肯定是第二小的...,就这样两两比较最终就可以把 tmpX 和 tmpY 合并成一个有序的文件tmpZ,多个这样的tmpZ再次合并...,最终就可以把所有的数据合并成一个有序的大文件。

20.png

文件排序很慢,还有其他办法吗

通过上面的排序流程我们知道,如果要排序的数据很大,超过 sort_buffer 的大小,那么就需要文件排序,文件排序涉及到分批排序与合并,很耗时,造成这个问题的根本原因是 sort_buffer 不够用,不知道你发现没有我们的 friend_name 需要排序,但是却把 friend_addr 也塞进了 sort_buffer 中,这样单行数据的大小就等于 friend_name 的长度 + friend_addr 的长度,能否让 sort_buffer 中只存 friend_name 字段,这样的话,整体的利用空间就大了,不一定用得到到临时文件。没错,这就是接下来要说的另一种排序优化rowid排序。

rowid 排序的思想就是把不需要的数据不要放到 sort_buffer 中,让 sort_buffer 中只保留必要的数据,那么你认为什么是必要的数据呢?只放 friend_name?这肯定不行,排序完了之后,friend_addr 怎么办?因此还要把主键id放进去,这样排完之后,通过 id 再回次表,拿到 friend_addr 即可,因此它的大致流程如下:

  • 根据 user_id 索引,查到目标数据,然后回表,只把 id 和 friend_name 放进 sort_buffer 中

  • 重复1步骤,直至全部的目标数据都在 sort_buffer 中

  • 对 sort_buffer 中的数据按照 friend_name 字段进行排序

  • 排序后根据 id 再次回表查到 friend_addr 返回,直至返回1000条数据,结束。

19.png

这里面其实有几点需要注意的:

  • 这种方式需要两次回表的

  • sort_buffer 虽然小了,但是如果数据量本身还是很大,应该还是要临时文件排序的

那么问题来了,两种方式,MySQL 该如何选择?得根据某个条件来判断走哪种方式吧,这个条件就是进 sort_buffer 单行的长度,如果长度太大(friend_name + friend_addr的长度),就会采用 rowid 这种方式,否则第一种,长度的标准是根据 max_length_for_sort_data 来的,这个值默认是1024字节:

mysql> show variables like 'max_length_for_sort_data';
+--------------------------+-------+
| Variable_name          | Value |
+--------------------------+-------+
| max_length_for_sort_data | 1024  |
+--------------------------+-------+

不想回表,不想再次排序

其实不管是上面哪种方法,他们都需要回表+排序,回表是因为二级索引上没有目标字段,排序是因为数据不是有序的,那如果二级索引上有目标字段并且已经是排序好的了,那不就两全其美了嘛。

没错,就是联合索引,我们只需要建立一个 (user_id,friend_name,friend_addr)的联合索引即可,这样我就可以通过这个索引拿到目标数据,并且friend_name已经是排序好的,同时还有friend_addr字段,一招搞定,不需要回表,不需要再次排序。因此对于上述的sql,它的大致流程如下:

  • 通过联合索引找到user_id=10086的数据,然后读取对应的 friend_name 和 friend_addr 字段直接返回,因为 friend_name 已经是排序好的了,不需要额外处理

  • 重复第一步骤,顺着叶子节点接着向后找,直至找到第一个不是10086的数据,结束。

18.png

联合索引虽然可以解决这种问题,但是在实际应用中切不可盲目建立,要根据实际的业务逻辑来判断是否需要建立,如果不是经常有类似的查询,可以不用建立,因为联合索引会占用更多的存储空间和维护开销。

总结

  • 对于 order by 没有用到索引的时候,这时 explain 中 Extra 字段大概是会出现 using filesort 字眼

  • 出现 using filesort 的时候也不用太慌张,如果本身数据量不大,比如也就几十条数据,那么在 sort buffer 中使用快排也是很快的

  • 如果数据量很大,超过了 sort buffer 的大小,那么是要进行临时文件排序的,也就是归并排序,这部分是由 MySQL 优化器决定的

  • 如果查询的字段很多,想要尽量避免使用临时文件排序,可以尝试设置下 max_length_for_sort_data 字段的大小,让其小于所有查询字段长度的总和,这样放入或许可以避免,但是会多一次回表操作

  • 实际业务中,我们也可以给经常要查询的字段组合建立个联合索引,这样既不用回表也不需要单独排序,但是联合索引会占用更多的存储和开销

  • 大量数据查询的时候,尽量分批次,提前 explain 来观察 sql 的执行计划是个不错的选择。

推荐学习:mysql视频教程

以上是你真的了解MySQL的order by吗的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:掘金。如有侵权,请联系admin@php.cn删除
MySQL的角色:Web应用程序中的数据库MySQL的角色:Web应用程序中的数据库Apr 17, 2025 am 12:23 AM

MySQL在Web应用中的主要作用是存储和管理数据。1.MySQL高效处理用户信息、产品目录和交易记录等数据。2.通过SQL查询,开发者能从数据库提取信息生成动态内容。3.MySQL基于客户端-服务器模型工作,确保查询速度可接受。

mysql:构建您的第一个数据库mysql:构建您的第一个数据库Apr 17, 2025 am 12:22 AM

构建MySQL数据库的步骤包括:1.创建数据库和表,2.插入数据,3.进行查询。首先,使用CREATEDATABASE和CREATETABLE语句创建数据库和表,然后用INSERTINTO语句插入数据,最后用SELECT语句查询数据。

MySQL:一种对数据存储的初学者友好方法MySQL:一种对数据存储的初学者友好方法Apr 17, 2025 am 12:21 AM

MySQL适合初学者,因为它易用且功能强大。1.MySQL是关系型数据库,使用SQL进行CRUD操作。2.安装简单,需配置root用户密码。3.使用INSERT、UPDATE、DELETE、SELECT进行数据操作。4.复杂查询可使用ORDERBY、WHERE和JOIN。5.调试需检查语法,使用EXPLAIN分析查询。6.优化建议包括使用索引、选择合适数据类型和良好编程习惯。

MySQL初学者友好吗?评估学习曲线MySQL初学者友好吗?评估学习曲线Apr 17, 2025 am 12:19 AM

MySQL适合初学者,因为:1)易于安装和配置,2)有丰富的学习资源,3)SQL语法直观,4)工具支持强大。尽管如此,初学者需克服数据库设计、查询优化、安全管理和数据备份等挑战。

SQL是一种编程语言吗?澄清术语SQL是一种编程语言吗?澄清术语Apr 17, 2025 am 12:17 AM

是的,sqlisaprogramminglanguges pecialized fordatamanage.1)它具有焦点,focusingonwhattoachieveratherthanhow.2)sqlisessential forquerying forquerying,插入,更新,更新,和detletingdatainrelationalDatabases.3)

解释酸的特性(原子,一致性,隔离,耐用性)。解释酸的特性(原子,一致性,隔离,耐用性)。Apr 16, 2025 am 12:20 AM

ACID属性包括原子性、一致性、隔离性和持久性,是数据库设计的基石。1.原子性确保事务要么完全成功,要么完全失败。2.一致性保证数据库在事务前后保持一致状态。3.隔离性确保事务之间互不干扰。4.持久性确保事务提交后数据永久保存。

MySQL:数据库管理系统与编程语言MySQL:数据库管理系统与编程语言Apr 16, 2025 am 12:19 AM

MySQL既是数据库管理系统(DBMS),也与编程语言紧密相关。1)作为DBMS,MySQL用于存储、组织和检索数据,优化索引可提高查询性能。2)通过SQL与编程语言结合,嵌入在如Python中,使用ORM工具如SQLAlchemy可简化操作。3)性能优化包括索引、查询、缓存、分库分表和事务管理。

mySQL:使用SQL命令管理数据mySQL:使用SQL命令管理数据Apr 16, 2025 am 12:19 AM

MySQL使用SQL命令管理数据。1.基本命令包括SELECT、INSERT、UPDATE和DELETE。2.高级用法涉及JOIN、子查询和聚合函数。3.常见错误有语法、逻辑和性能问题。4.优化技巧包括使用索引、避免SELECT*和使用LIMIT。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它们
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),