order by工作原理分析

文章导读

order by大纲.png

前言

在日常写代码的工作中,排序无处不在,各种编程语言都提供了排序的实现,比如java的Arrays.sort(),golang的sort.Sort(),数据库也会有数据库的排序,我们天天写的sql,经常都有按照指定字段排序的需求,所以都会使用order by语句,那么你真的了解order by的排序逻辑吗?本篇文章就是带领大家一起探究一下Mysql的order by排序是如何实现的。

准备工作

创建一个市民表,

CREATE TABLE `t` ( `id` int(11) NOT NULL, `city` varchar(16) NOT NULL, `name` varchar(16) NOT NULL, `age` int(11) NOT NULL, `addr` varchar(128) DEFAULT NULL, PRIMARY KEY (`id`), KEY `city` (`city`) ) ENGINE=InnoDB;
复制代码

如果我们要查询城市是“杭州”的所有人的名字,并且按照姓名排序返回前1000个人的姓名、年龄。

我们的sql语句是:

select city,name,age from t where city='杭州' order by name limit 1000 ;
复制代码

sort buffer

我们使用explain命令查看一下这个sql的执行计划:

执行计划1.png

其中Extra字段中的“Using filesort”就表示需要排序,mysql会为每个线程分配一块内存用于排序,称为:sort buffer;

sort_buffer_size参数:mysql为排序开辟的内存sort buffer的大小;

  • 如果排序的数据小于sort_buffer_size,排序就在内存中完成;
  • 如果排序的数据大于sort_buffer_size,就得利用磁盘的临时文件使用外部排序,外排一般使用归并排序算法。

再来看看city字段建立的索引示意图:

img

扫描二维码关注公众号,回复: 14530374 查看本文章

无序数据排序

全字段排序

select city,name,age from t where city='杭州' order by name limit 1000 ;
复制代码

执行流程

观察上面的sql,可以发现查询的字段并不多,此时会走全字段排序的执行流程:

img

  1. 初始化 sort_buffer,确定放入 name、city、age 这三个字段;
  2. 从索引 city 找到第一个满足 city='杭州’条件的主键 id,也就是图中的 ID_X;
  3. 到主键 id 索引取出整行,取 name、city、age 三个字段的值,存入 sort_buffer 中;
  4. 从索引 city 取下一个记录的主键 id;重复步骤 3、4 直到 city 的值不满足查询条件为止,对应的主键 id 也就是图中的 ID_Y;
  5. 对 sort_buffer 中的数据按照字段 name 做快速排序;按照排序结果取前 1000 行返回给客户端。

rowid排序

select * from t where city='杭州' order by name limit 1000 ;
复制代码

执行流程

如果需要查询的字段非常多,此时会走rowid排序的执行流程:

img

  1. 初始化sort_buffer, 确定放入两个字段,即name和id;
  2. 从索引city找到第一个满足city=“杭州”条件的主键id, 也就是图中的ID_X;
  3. 到主键id索引取整行,取name, id 这两个字段,存入sort_buffer中;
  4. 从索引city取下一个记录的主键id;
  5. 重复3,4直到city的值不满足查询条件为止,对应的主键id也就是途中ID_Y;
  6. 对sort_buffer中的数据按照字段name做快速排序;
  7. 遍历排序结果,取前1000行,并按照id值回表中取出city、name和age三个字段返回给客户端。

ps:

当查询字段多,mysql会选择rowid的方式,这样可以避免外部排序,但是对比全字段排序,rowid的方式多了回表。

全字段排序 vs rowid排序

全字段排序 rowid排序
将要查询的字段数据全部放在sort_buffer中, 然后进行排序, 如果内存不足,使用外排; 只将排序字段和主键id的数据放在sort_buffer中, 然后排序, 最后根据主键id将其他数据回表查询出来;
适用于innodb表 适用于memory表

mysql的设计思想:

如果内存够,就要多利用内存,尽量减少磁盘访问,也就是尽量减少使用rowid,因为rowid需要回表;

对于innodb表来说,rowid排序要求回表,会增加磁盘io,因此不会被优先选择;

但是rowid排序也有适用的场景,比如内存表,即使回表,也是查询内存;

如何避免排序

覆盖索引

根据上面全字段和rowid的排序流程,如果查询出来的数据本身就是排序好的,那么是不是避免了排序,从而就能提高sql的查询速度,索引是有序的,如果能走覆盖索引,那么就并不需要高成本的去排序;

给city name age这三个字段加上联合索引:

alter table t add index city_user(city, name, age);
复制代码

然后在执行一下sql

select city,name,age from t where city='杭州' order by name limit 1000 ;
复制代码

查看一下执行计划:

执行计划覆盖索引.png 可以看出extra字段没有using filesort,不需要排序,并且也使用到了覆盖索引,性能提升很多。

随机排序

order by rand()

接下来再来分析一下随机排序,也就是order by rand(),探究一下order by rand()的排序逻辑。

准备工作:

--创建单词表
CREATE TABLE `words` ( `id` int(11) NOT NULL AUTO_INCREMENT, `word` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB;
--创建1w条数据
复制代码

执行sql:

select word from words order by rand() limit 3;
复制代码

查看执行计划:

执行计划2.png 可以看出使用了临时表和排序,临时表其实就是内存表,那么它的排序会如何考虑呢?

对于临时表,本身就存储在内存,所以即使回表,也是查询内存,不需要访问磁盘,速度很可能,mysql会选择rowid排序。

执行流程

  1. 一个临时表,该表使用的memory引擎,第一个字段是double类型,记为R,第二个字段是varchar类型,记为W;
  1. 从words表中,按主键顺序取出所有的word值,对每个word值,调用rand()函数,生成一个小数,并将这个小数存入R字段,word值存入W字段,是全表扫描;
  1. 临时表根据rowid方式进行排序,初始化sort_buffer,使用rowid排序,两个字段,一个R字段,一个是位置信息字段;
  1. 在sort_buffer中根据R字段排序,排序完成,根据前三个结果的位置信息,依次到临时表取出word值,返回。

ps:

位置信息字段是什么意思?其实也就是mysql的表是怎么定位一行数据的?

  • 对于有主键的innodb表来说,这个rowid就是主键id;
  • 对于没有主键的innodb表来说,这个rowid就是由系统生成的;
  • memory引擎不是索引表,可以认为就是一个数组,rowid就是数组的下标;

内存临时表&磁盘临时表

刚刚提到内存临时表,那么是不是所有的临时表都是基于内存呢?其实也不是,mysql也有一个参数判断tmp_table_size,默认是16M,如果内存表超过了这个size,那么就会转成磁盘临时表。

磁盘临时表使用的就是innodb引擎,排序会优先选择全字段排序;

这个sql只需要取前三个数据,所以排序算法会优先选择堆排序,而不是归并排序,这也是mysql5.6以后的优化;

随机排序的优化

分析了order by rand()的流程,发现成本也是比较高的,所以可以换一种思路,不管我们的表的主键id是不是连续,都有办法,先找出随机的三个index;

总结

  1. mysql的order by是一个成本比较高的操作,学习了order by()的工作原理,这也是日常工作中sql优化的一个考虑点;
  2. 平时工作中大部分排序需求都是基于分页的,limit都比较小,但是select的字段比较多,然后表大部分是基于innodb引擎,innodb优先选择全字段排序,如果sort_buffer_size不够的话,可能也会有使用外部排序的可能,所以也可以适当调整一下sort_buffer_size参数大小;
  3. mysql根据不同的排序场景也做出了很多优化,比如排序算法并不只是归并,也有堆排序;
  4. mysql的设计思想:一切的优化都是为了减少磁盘io;

参考


time.geekbang.org/column/arti…

chenjiabing666.github.io/2020/04/19/…

gsmtoday.github.io/2019/02/09/…

猜你喜欢

转载自juejin.im/post/7154912038517473311