天天写order by,你知道Mysql底层执行原理吗?

前言

  • 在实际的开发中一定会碰到根据某个字段进行排序后来显示结果的需求,但是你真的理解order by在 MySQL 底层是如何执行的吗?
  • 假设你要查询城市是苏州的所有人名字,并且按照姓名进行排序返回前 1000 个人的姓名、年龄,这条 sql 语句应该如何写?
  • 首先创建一张用户表,sql 语句如下:
CREATE TABLE user (
  id int(11) NOT NULL,
  city varchar(16) NOT NULL,
  name varchar(16) NOT NULL,
  age int(11) NOT NULL,
  PRIMARY KEY (id),
  KEY city (city)
) ENGINE=InnoDB;
  • 则上述需求的 sql 查询语句如下:
select city,name,age from user where city='苏州' order by name limit 1000;
  • 这条 sql 查询语句相信大家都能写出来,但是你了解它在 Mysql 底层的执行流程吗?今天陈某来大家聊一聊这条 sql 语句是如何执行的以及有什么参数会影响执行的流程 。
  • 本篇文章分为如下几个部分进行详细的阐述:全字段排序rowid 排序全字段排序 VS rowid 排序如何避免排序
全字段排序
  • 前面聊过索引能够避免全表扫描,因此我们给city这个字段上添加了索引,当然城市的字段很小,不用考虑字符串的索引问题
  • 此时用Explain来分析一下的这条查询语句的执行情况,结果如下图:

天天写order by,你知道Mysql底层执行原理吗?

文章插图
 
  • Extra这个字段中的Using filesort表示的就是需要排序,MySQL 会给每个线程分配一块内存用于排序,称为sort_buffer 。
  • 既然使用了索引进行查询,我们来简单的画一下city这棵索引树的结构,如下图:

天天写order by,你知道Mysql底层执行原理吗?

文章插图
 
  • 从上图可以看出,满足city='苏州'是从ID3到IDX这些记录 。
  • 通常情况下,此条 sql 语句执行流程如下:我们称这个排序过程为全字段排序,执行的流程图如下: !
 
1.初始化 sort_buffer,确定放入 name、city、age 这三个字段 。2.从索引 city 找到第一个满足`city='苏州'`条件的`主键id`,也就是图中的`ID3` 。3.到`主键id索引`取出整行,取`name`、`city`、`age`三个字段的值,存入`sort_buffer`中 。4.从索引`city`取下一个记录的主键 id 。5.重复步骤 3、4 直到 city 的值不满足查询条件为止,对应的主键 id 也就是图中的`IDX` 。6.对`sort_buffer`中的数据按照字段`name`做快速排序 。7.按照排序结果取前 1000 行返回给客户端 。
  • 图中按name排序这个动作,可能在内存中完成,也可能需要使用外部排序,这取决于排序所需的内存和参数sort_buffer_size 。
  • sort_buffer_size:就是 MySQL 为排序开辟的内存(sort_buffer)的大小 。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成 。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序 。
rowid 排序
  • 在上面这个算法过程里面,只对原表的数据读了一遍,剩下的操作都是在sort_buffer和临时文件中执行的 。但这个算法有一个问题,就是如果查询要返回的字段很多的话,那么sort_buffer里面要放的字段数太多,这样内存里能够同时放下的行数很少,要分成很多个临时文件,排序的性能会很差 。
  • 所以如果单行很大,这个方法效率不够好 。
  • 我们可以修改一个max_length_for_sort_data这个参数使其使用另外一种算法 。max_length_for_sort_data,是 MySQL 中专门控制用于排序的行数据的长度的一个参数 。它的意思是,如果单行的长度超过这个值,MySQL 就认为单行太大,要换一个算法 。
  • city、name、age 这三个字段的定义总长度是36,我把max_length_for_sort_data设置为 16,我们再来看看计算过程有什么改变 。设置的 sql 语句如下:
SET max_length_for_sort_data = https://www.isolves.com/it/sjk/MYSQL/2020-04-15/16;