当前位置:

首页 > 编程开发 > MySQL的order by怎么正确使用

MySQL的order by怎么正确使用

排序这个词,我的第一感觉是几乎所有App都有排序的地方,淘宝商品有按照购买时间的排序、B站的评论有按照热度排序的...,当然我们今天说的并不是大数据下该如何优雅的排序,如何提升排序性能的问题,我们说一说MySQL中的排序。对于MySQL,一说到排序,你第一时间想到的是什么?关键字orderby?orderby的字段最好有索引?叶子结点已经是顺序的?还是说尽量不要在MySQL内部排序?事情的起因现在假设有一张用户的朋友表:CREATETABLE`user`(`id`int(10)AUTO_INCREMENT

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条,结束。

MySQL的order by怎么正确使用

一切看起来很丝滑,但是 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] < tmpY[0],那么 tmpX[0] 肯定是最小的,然后 tmpX[1] 和 tmpY[0] 比较,如果 tmpX[1] > tmpY[0],那么 tmpY[0] 肯定是第二小的...,就这样两两比较最终就可以把 tmpX 和 tmpY 合并成一个有序的文件tmpZ,多个这样的tmpZ再次合并...,最终就可以把所有的数据合并成一个有序的大文件。

MySQL的order by怎么正确使用

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

通过上面的排序流程我们知道,如果要排序的数据很大,超过 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条数据,结束。

MySQL的order by怎么正确使用

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

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

  • 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的数据,结束。

MySQL的order by怎么正确使用

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

本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
相关文章 更多
C++动态数组初始化怎么写?常用语句与代码示例
C++动态数组初始化怎么写?常用语句与代码示例

深入解析C++中动态数组的初始化机制,涵盖new操作符的不同用法、基本类型与类对象的初始化差异,以及为何在现代C++开发中应优先使用std::vector。

using namespace 使用中遇到的问题怎么解决
using namespace 使用中遇到的问题怎么解决

命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

c语言函数递归 实操经验总结:这些技巧很实用
c语言函数递归 实操经验总结:这些技巧很实用

理解递归的基本原理在C语言中,递归是一种函数调用自身的编程技术。要掌握它,首先需要理解其核心思想:将一个复杂的大问题,分解为一个或几个与原问题相似但规模更小的子问题,直到子问题足够简单,可以直接求解。这个过程通常包含两个关键部分:递归出口和递归体。递归出口定义了问题何时不再继续分解,即最简单、可直接

c语言函数递归 怎么选?常见方案对比分析
c语言函数递归 怎么选?常见方案对比分析

递归函数的基本概念与适用场景在C语言编程中,递归是一种函数调用自身的编程技巧。它并非适用于所有问题,但在处理某些具有自相似结构的问题时,能提供极其清晰和优雅的解决方案。递归的核心思想是将一个大规模问题分解为一个或多个同类型但规模更小的子问题,直到子问题简单到可以直接求解。典型的适用场景包括树形结构的

Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解
Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解

理解内存管理的基石在Objective-C的编程世界中,内存管理是开发者必须掌握的核心技能之一。它直接关系到应用的性能、稳定性与资源利用效率。与一些采用自动垃圾回收机制的语言不同,Objective-C在很长一段时间里,依赖一套基于引用计数的、需要开发者部分介入的管理规则。这套规则的核心思想是明确的

如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏
如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏

理解 dealloc 的角色与时机在 iOS 应用开发中,内存管理是保障应用性能与稳定性的基石。dealloc 方法是 Objective-C 中对象生命周期结束时的关键回调,它标志着对象即将被系统回收内存。正确理解其触发时机至关重要:当一个对象的引用计数降为零时,运行时系统会自动调用该对象的 de

深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制
深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制

内存管理的基石在Objective-C的世界里,内存管理是开发者必须掌握的核心技能之一。作为一门在手动引用计数(MRC)时代诞生的语言,Objective-C要求程序员对对象的生命周期有清晰的认识。dealloc方法正是这一生命周期中至关重要的终点站。它是一个实例方法,当对象的引用计数降为零时,系统

理解 native2ascii:Java 国际化开发中的字符编码工具
理解 native2ascii:Java 国际化开发中的字符编码工具

native2ascii 工具的基本定位在Ja va应用程序的国际化与本地化开发过程中,处理非拉丁字符集是一个常见且关键的环节。Ja va内部使用Unicode字符集来统一表示全球各种语言的文字,但其属性文件(.properties)在历史上要求使用ASCII编码,或者更准确地说,要求非ASCII字

如何使用 native2ascii 转换中文字符为 Unicode 转义序列
如何使用 native2ascii 转换中文字符为 Unicode 转义序列

理解 native2ascii 工具的基本用途在软件开发,特别是涉及国际化处理的场景中,开发者常常需要处理不同编码的文本资源。native2ascii 是 Ja va 开发工具包(JDK)中提供的一个命令行实用程序,其主要功能是将包含本地字符编码(非ASCII字符)的文件,转换为包含 Unicode

Java native2ascii 命令详解:解决属性文件乱码问题
Java native2ascii 命令详解:解决属性文件乱码问题

native2ascii 命令的由来与作用在Ja va开发中,处理国际化资源文件是一个常见需求。资源文件通常以.properties格式存储,用于支持多语言界面。然而,Ja va属性文件默认采用ISO-8859-1字符集编码,这导致了一个直接的问题:当文件中包含非拉丁字符(如中文、日文、韩文等)时,

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件
macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

Mac软件 更多
灵活计算器
灵活计算器
macOS/iOS/Android

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师
macOS

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

WINDOWS 更多
Windows 10
Windows 10
Windows

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘
Windows/macOS/iOS/Android

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。