后端程序员必备:攻克order by,揭秘_android orderby(2)

id_card VARCHAR ( 20 ) NOT NULL COMMENT ‘身份证号码’,

name VARCHAR ( 64 ) NOT NULL COMMENT ‘姓名’,

age INT ( 4 ) NOT NULL COMMENT ‘年龄’,

city VARCHAR ( 64 ) NOT NULL COMMENT ‘城市’,

PRIMARY KEY ( id),

INDEX idx_city ( city )

) ENGINE = INNODB COMMENT ‘员工表’;


表数据如下:


![](https://img-blog.csdnimg.cn/img_convert/430e6303da0967057618596e80ce0d56.png)


我们现在有这么一个需求:**查询前10个,来自深圳员工的姓名、年龄、城市,并且按照年龄小到大排序**。对应的 SQL 语句就可以这么写:



select name,age,city from staff where city = ‘深圳’ order by age limit 10;


这条语句的逻辑很清楚,但是它的**底层执行流程**是怎样的呢?


order by 工作原理




---


![](https://img-blog.csdnimg.cn/img_convert/df153efb439a05ec9353cb41c755f23b.png)


#### explain 执行计划


我们先用**Explain**关键字查看一下执行计划


![](https://img-blog.csdnimg.cn/img_convert/9bf1cb6f9b6651eebab050bd6e1c0c57.png)


* 执行计划的**key**这个字段,表示使用到索引idx\_city
* Extra 这个字段的 **Using index condition** 表示索引条件
* Extra 这个字段的 **Using filesort**表示用到排序


我们可以发现,这条SQL使用到了索引,并且也用到排序。那么它是**怎么排序**的呢?


#### 全字段排序


MySQL 会给每个查询线程分配一块小**内存**,用于**排序**的,称为 **sort\_buffer**。什么时候把字段放进去排序呢,其实是通过`idx_city`索引找到对应的数据,才把数据放进去啦。


我们回顾下索引是怎么找到匹配的数据的,现在先把索引树画出来吧,**idx\_city**索引树如下:


![](https://img-blog.csdnimg.cn/img_convert/5a5cb04e5a2488dee7528f36514be11c.png)


idx\_city索引树,叶子节点存储的是**主键id**。 还有一棵id主键聚族索引树,我们再画出聚族索引树图吧:


![](https://img-blog.csdnimg.cn/img_convert/e1ad8545d7680d72b7cfafc356c3f52f.png)


**我们的查询语句是怎么找到匹配数据的呢**?先通过**idx\_city**索引树,找到对应的主键id,然后再通过拿到的主键id,搜索**id主键索引树**,找到对应的行数据。


加上**order by**之后,整体的执行流程就是:


1. MySQL 为对应的线程初始化**sort\_buffer**,放入需要查询的name、age、city字段;
2. 从**索引树idx\_city**, 找到第一个满足 city='深圳’条件的主键 id,也就是图中的id=9;
3. 到**主键 id 索引树**拿到id=9的这一行数据, 取name、age、city三个字段的值,存到sort\_buffer;
4. 从**索引树idx\_city** 拿到下一个记录的主键 id,即图中的id=13;
5. 重复步骤 3、4 直到**city的值不等于深圳**为止;
6. 前面5步已经查找到了所有**city为深圳**的数据,在 sort\_buffer中,将所有数据根据age进行排序;
7. 按照排序结果取前10行返回给客户端。


执行示意图如下:


![](https://img-blog.csdnimg.cn/img_convert/132e684d487a566fa79d926529010af4.png)


将查询所需的字段全部读取到sort\_buffer中,就是**全字段排序**。这里面,有些小伙伴可能会有个疑问,把查询的所有字段都放到sort\_buffer,而sort\_buffer是一块内存来的,如果数据量太大,sort\_buffer放不下怎么办呢?


#### 磁盘临时文件辅助排序


实际上,sort\_buffer的大小是由一个参数控制的:**sort\_buffer\_size**。如果要排序的数据小于sort\_buffer\_size,排序在**sort\_buffer** 内存中完成,如果要排序的数据大于sort\_buffer\_size,则**借助磁盘文件来进行排序**


如何确定是否使用了磁盘文件来进行排序呢? 可以使用以下这几个命令



打开optimizer_trace,开启统计

set optimizer_trace = “enabled=on”;

执行SQL语句

select name,age,city from staff where city = ‘深圳’ order by age limit 10;

查询输出的统计信息

select * from information_schema.optimizer_trace


可以从 **number\_of\_tmp\_files** 中看出,是否使用了临时文件。


![](https://img-blog.csdnimg.cn/img_convert/dd3e3f68f022b358d98b9358557a8dbc.png)


**number\_of\_tmp\_files** 表示使用来排序的磁盘临时文件数。如果number\_of\_tmp\_files>0,则表示使用了磁盘文件来进行排序。


使用了磁盘临时文件,整个排序过程又是怎样的呢?


1. 从**主键Id索引树**,拿到需要的数据,并放到**sort\_buffer内存**块中。当sort\_buffer快要满时,就对sort\_buffer中的数据排序,排完后,把数据临时放到磁盘一个小文件中。
2. 继续回到主键 id 索引树取数据,继续放到sort\_buffer内存中,排序后,也把这些数据写入到磁盘临时小文件中。
3. 继续循环,直到取出所有满足条件的数据。最后把磁盘的临时排好序的小文件,合并成一个有序的大文件。


**TPS:** 借助磁盘临时小文件排序,实际上使用的是**归并排序**算法。


小伙伴们可能会有个疑问,既然**sort\_buffer**放不下,就需要用到临时磁盘文件,这会影响排序效率。那为什么还要把排序不相关的字段(name,city)放到sort\_buffer中呢?只放排序相关的age字段,它**不香**吗? 可以了解下**rowid 排序**。


#### rowid 排序


rowid 排序就是,只把查询SQL**需要用于排序的字段和主键id**,放到sort\_buffer中。那怎么确定走的是全字段排序还是rowid 排序排序呢?


实际上有个参数控制的。这个参数就是**max\_length\_for\_sort\_data**,它表示MySQL用于排序行数据的长度的一个参数,如果单行的长度超过这个值,MySQL 就认为单行太大,就换rowid 排序。我们可以通过命令看下这个参数取值。



show variables like ‘max_length_for_sort_data’;


![](https://img-blog.csdnimg.cn/img_convert/5af22220522319bc4c5da6a9cd67209c.png)


**max\_length\_for\_sort\_data** 默认值是1024。因为本文示例中name,age,city长度=64+4+64 =132 < 1024, 所以走的是全字段排序。我们来改下这个参数,改小一点,



修改排序数据最大单行长度为32

set max_length_for_sort_data = 32;

执行查询SQL

select name,age,city from staff where city = ‘深圳’ order by age limit 10;


使用rowid 排序的话,整个SQL执行流程又是怎样的呢?


1. MySQL 为对应的线程初始化**sort\_buffer**,放入需要排序的age字段,以及主键id;
2. 从**索引树idx\_city**, 找到第一个满足 city='深圳’条件的主键 id,也就是图中的id=9;
3. 到**主键 id 索引树**拿到id=9的这一行数据, 取age和主键id的值,存到sort\_buffer;
4. 从**索引树idx\_city** 拿到下一个记录的主键 id,即图中的id=13;
5. 重复步骤 3、4 直到**city的值不等于深圳**为止;
6. 前面5步已经查找到了所有city为深圳的数据,在 **sort\_buffer**中,将所有数据根据age进行排序;
7. 遍历排序结果,取前10行,并按照 id 的值**回到原表**中,取出city、name 和 age 三个字段返回给客户端。


执行示意图如下:


![](https://img-blog.csdnimg.cn/img_convert/2b76db647dbdd6c5f8b6d4782a0f06b8.png)


对比一下**全字段排序**的流程,rowid 排序多了一次**回表**。



> 
> 什么是回表?拿到主键再回到主键索引查询的过程,就叫做回表
> 
> 
> 


我们通过**optimizer\_trace**,可以看到是否使用了rowid排序的:



尾声

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

  • 思维脑图
  • 性能优化学习笔记


  • 性能优化视频

    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

,理论看多了总要实践的。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值