Mysql系列-Order By

353 阅读5分钟

前言

面试过程中对于排序方面的考察,一般就集中在order by和group by上,今天我们一起研究下order by相关的知识点,对于order By我们回答的点都有哪些,相关面试题如下,小伙伴试着回答一下吧

  • MySQL中的group by,order by 如何进行优化的
  • group by和order by的区别
  • 排序时候order by执行起来比较慢,怎么进行优化

概念

我们在查询的时候,如果需要对查询中读取的数据进行排序,我们就需要使用order by 字段进行排序,排序的语法是

SELECT field1, field2,...fieldN FROM table_name ORDER BY field1 desc/ASC 

今天我们一起研究下这段语句执行到数据库的时候,Mysql 的底层流程是怎么样的

排序算法

我们通过一个具体的案例来具体分析一下相关流程,建表语句如下所示

CREATE TABLE user (
  id int(11) AUTO_INCREMENT COMMENT '主键id',
  city varchar(16) NOT NULL COMMENT '城市',
  name varchar(16) NOT NULL COMMENT '名称', 
  age int(11) NOT NULL COMMENT '年龄',
  PRIMARY KEY (id),
  KEY city (city)
) ENGINE=InnoDB;
insert into `user` (city,name,age) VALUES ('北京','程序员fly',20);
insert into `user` (city,name,age) VALUES ('北京','小红',22);
insert into `user` (city,name,age) VALUES ('上海','小飞',22);
insert into `user` (city,name,age) VALUES ('苏州','大飞',30);
insert into `user` (city,name,age) VALUES ('杭州','程序员fly',26);
insert into `user` (city,name,age) VALUES ('广州','程序员fly',21);
insert into `user` (city,name,age) VALUES ('长春','程序员fly',20);

全字段排序

现在有这么个需求,按照年龄从小到大,查询前三个城市在北京的用户相关信息,我们写sql应该会这样写,这条sql执行简单,接下来我们通过explain关键字来看一下SQL的执行计划

select * from `user` where `city`='北京' order by age limit 3;

执行计划 执行计划中,我们可以看到,using fileSort进行排序相关操作了,这个排序操作具体如何呢,相关流程如下

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

全字段排序流程

Mysql会为每一个查询的线程专门分配一块内存(sort_buffer)去干排序这件事,内存大小有sort_buffer_size控制,上面流程中我们将回表取的id,city,name、age全部放到sort_buffer中,我们称为上面流程所用的算法为全字段排序

这里小伙伴们是否有这样的疑问,如果我们查的数据sort_buffer放不下该怎么办,mysql其实这个时候会借用磁盘临时文件辅助排序,这里会用到一个归并算法,具体流程如下

  1. 首先从索引city找到第一个满足city='北京’条件的主键id进行回表操作,查找相关的数据
  2. 将查询的数据放入到sort_buffer中,当sort_buffer快满的时候,就在sort_buffer里面先对这部分数据进行排序,排序好的记录临时放入磁盘小文件中,继续往sort_buffer读取数据
  3. 经过第2步就会得到很多有序的小文件,利用归并排序合并成一个大文件,完成整个排序过程

rowId排序

​ 全字段排序是将id,city,name、age全部放入到sort_buffer里面进行排序,我们sql里面写的排序规则是order by age按照年龄排序,如果我们只需要将age放入sort_buffer中,这样不就能装下更多的值(因为sort_buffer里面放入字段越多,存放的条数就会越少,就很有可能利用磁盘临时文件进行排序,磁盘肯定没内存快嘛)所以Mysql又提供了一种排序算法rowid排序算法,具体流程如下

  1. 初始化sort_buffer,确定放入两个字段,即age和id;
  2. 从索引city找到第一个满足city='北京’条件的主键id;
  3. 到主键id索引查找到整行,取age、id这两个字段(没有取name、city、age),存入sort_buffer中;
  4. 从索引city取下一个记录的主键id;
  5. 重复步骤3、4直到不满足city='北京’条件为止;
  6. 对sort_buffer中的数据按照字段age进行排序;
  7. 遍历排序结果,取前3行,因为sort只要age,id这两个字段,但是我们需要是的id,name,city,age相关的信息,这里需要回表去主键索引树上根据id读相关信息返回(多了一步回表操作

rowid排序

优化思路

调整sort_buffer的大小

​ 内存肯定比磁盘快的,Mysql有个思想,如果内存够的话就多用内存,尽量减少磁盘访问,也因此我们尽量把sort_buffer调大一点

避免Rowid排序

对于InnoDB表来说,rowid排序会多一次回表操作,会增加磁盘读,我们可以适当调整下这个配置参数 max_length_for_sort_data:参数含义如下:如果放入sort_buffer中的字段长度大于这个值,Mysql就会使用rowid排序,Mysql默认为为1KB(开发过程中尽量别写select *)

使用联合索引

排序是因为数据是乱序的所以需要排序,如果能够保证从city这个索引取出的行,天然的按照age排序,这样不仅不需要排序了,我们上面阶段学习过程中我们知道Mysql的B+树是有序的,我们就可以建立(city,age)的联合索引,当city相同的时候,age是天然有序的,避免排序,B+树存储结构如图所示

联合索引explain

select * from `user` where `city`='北京' order by age limit 3; //order by age ,age有序的,不再需要

通过explain关键字我们看到,当建立联合索引(city,age的时候,执行计划Extra中不再出现using fileSort,说明没用fileSort排序相关操作。

联合索引

闲谈

感觉有帮助的同学还请点赞关注,这将对我是很大的鼓励~,公众号有自己开始总结的一系列文章,需要的小伙伴还请关注下个人公众号程序员fly,希望能一起成长。

参考链接

www.cnblogs.com/Chenjiabing…

juejin.cn/post/684490…

time.geekbang.org/column/intr…