MySQL_order by 是怎么工作的

order by 是怎么工作的

学习检测

  1. order by 是怎么工作(流程)的?

  2. 全字段排序受什么影响?

  3. 全字段排序的流程?

  4. 全字段排序的缺点?

  5. rowid排序是什么?

  6. 什么条件触发rowid排序?

  7. rowid排序的和全字段排序流程的不同?

  8. rowid排序的缺点?

  9. 全字段排序和rowid排序比较?

  10. 避免排序的方法?

总结

  1. order by 取出数据在sort_buffer中进行排序,分为全字段排序和rowid排序

  2. 全字段排序sort_buffer_size影响和max_length_sort_data影响,如果查询字 段的长度大于max_length_sort_data会采用rowid排序,反之是全字段排序, 如果sort_buffer_size越小,number_of_tmp_files(用于排序的临时文件)就越 多

  3. 以 select city,name,age from t where city=’’ order by name limit 1000;为例

    • 申请sort_buffer,确认放入查询字段,

    • 到查询字段city的索引树,查找满足条件的行数据,取出city、name、age字段放入sort_buffer

    • 在到city索引书查找下一条记录,直到不满足条件的数据

    • sort_buffer根据name排序,返回前1000条

  4. 全字段排序有可能超多sort_buffer_size,造成number_of_tmp_files文件过多

  5. rowid排序是只把排序的字段和主键id放入sort_buffer中,然后排序好,根 据id取主键树取数据返回

  6. 当查询的字段长度超过max_length_sort_data的值的时候

  7. 以select city,name,age from t where city=’’ order by name limit 1000;为例

    • 申请sort_buffer,确定放入name、id字段

    • 到city索引树取出满足条件的id到主键索引树取出行数据,取出name、id放入sort_buffer中

    • 重复上面步骤到city索引树中查找满足条件的数据,直到不满足

    • 在sort_buffer中根据name排序

    • 取前1000条数据,根据主键id到主键索引树取出行数据中的name、city、age字段返回

    • 不同点在于多了一次回表的操作

  8. 在sort_buffer中排序完成后,要再次回表取出数据,回表的代价

  9. sort_buffer_size足够大,默认32m,不会回表查数据,rowid排序会有回表代 价

  10. 建立合适的联合索引,做到所以覆盖。order by 联合索引的部分,或者前缀 索引

查询城市是杭州的所有人名字,并且按照姓名返回1000个人的姓名和年龄

表结构定义

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;

查询sql语句

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

全字段排序

explain 执行计划
在这里插入图片描述

sort_buffer

extra 这个字段中的”using filesort”表示的就是排序,mysql会给每个线程分配一块内存用于排序,称为sort_buffer

sql 查询执行过程图
在这里插入图片描述
从图中可以看到,满足 city='杭州’条件的行,是从 ID_X 到 ID_(X+N) 的这些记录。

执行流程:

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

我们暂且把这个排序过程,称为全字段排序,执行流程的示意图如下所示
在这里插入图片描述
图中“按 name 排序”这个动作,可能在内存中完成,也可能需要使用外部排序,这取决于排序所需的内存和参数 sort_buffer_size。

sort_buffer_size,就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。

查看是否使用了临时文件:
在这里插入图片描述
number_of_tmp_files 表示的是,排序过程中使用的临时文件数。你一定奇怪,为什么需要 12 个文件?内存放不下时,就需要使用外部排序,外部排序一般使用归并排序算法。可以这么简单理解,MySQL 将需要排序的数据分成 12 份,每一份单独排序后存在这些临时文件中。然后把这 12 个有序文件再合并成一个有序的大文件。

如果 sort_buffer_size 超过了需要排序的数据量的大小,number_of_tmp_files 就是 0,表示排序可以直接在内存中完成。
在这里插入图片描述
否则就需要放在临时文件中排序。sort_buffer_size 越小,需要分成的份数越多,number_of_tmp_files 的值就越大。

接下来,我再和你解释一下图 4 中其他两个值的意思。

我们的示例表中有 4000 条满足 city='杭州’的记录,所以你可以看到 examined_rows=4000,表示参与排序的行数是 4000 行。

sort_mode 里面的 packed_additional_fields 的意思是,排序过程对字符串做了“紧凑”处理。即使 name 字段的定义是 varchar(16),在排序过程中还是要按照实际长度来分配空间的。

同时,最后一个查询语句 select @b-@a 的返回结果是 4000,表示整个执行过程只扫描了 4000 行。

这里需要注意的是,为了避免对结论造成干扰,我把 internal_tmp_disk_storage_engine 设置成 MyISAM。否则,select @b-@a 的结果会显示为 4001。

这是因为查询 OPTIMIZER_TRACE 这个表时,需要用到临时表,而 internal_tmp_disk_storage_engine 的默认值是 InnoDB。如果使用的是 InnoDB 引擎的话,把数据从临时表取出来的时候,会让 Innodb_rows_read 的值加 1。

rowid排序

全字段排序只对原表的数据读了一遍,剩下的操作都是在 sort_buffer 和临时文件中执行的。但这个算法有一个问题,就是如果查询要返回的字段很多的话,那么 sort_buffer 里面要放的字段数太多,这样内存里能够同时放下的行数很少,要分成很多个临时文件,排序的性能会很差。

如果 MySQL 认为排序的单行长度太大会怎么做呢?

SET max_length_for_sort_data = 16;

max_length_for_sort_data,是 MySQL 中专门控制用于排序的行数据的长度的一个参数。它的意思是,如果单行的长度超过这个值,MySQL 就认为单行太大,要换一个算法。

city、name、age 这三个字段的定义总长度是 36,我把 max_length_for_sort_data 设置为 16,我们再来看看计算过程有什么改变。

新的算法放入 sort_buffer 的字段,只有要排序的列(即 name 字段)和主键 id。

流程:

  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_Y
  6. 遍历结果,取出前1000行,并按照id值会表取出city、name、age三个字段返回给客户端

这个流程示意图如下,我们成为rowid排序
在这里插入图片描述
对比图 3 的全字段排序流程图你会发现,rowid 排序多访问了一次表 t 的主键索引,就是步骤 7。

需要说明的是,最后的“结果集”是一个逻辑概念,实际上 MySQL 服务端从排序后的 sort_buffer 中依次取出 id,然后到原表查到 city、name 和 age 这三个字段的结果,不需要在服务端再耗费内存存储结果,是直接返回给客户端的。

根据这个说明过程和图示,你可以想一下,这个时候执行 select @b-@a,结果会是多少呢?

现在,我们就来看看结果有什么不同。

首先,图中的 examined_rows 的值还是 4000,表示用于排序的数据是 4000 行。但是 select @b-@a 这个语句的值变成 5000 了。

因为这时候除了排序过程外,在排序完成后,还要根据 id 去原表取值。由于语句是 limit 1000,因此会多读 1000 行。(再次回表)
在这里插入图片描述
从 OPTIMIZER_TRACE 的结果中,你还能看到另外两个信息也变了。

n sort_mode 变成了 <sort_key, rowid>,表示参与排序的只有 name 和 id 这两个字段。

n number_of_tmp_files 变成 10 了,是因为这时候参与排序的行数虽然仍然是 4000 行,但是每一行都变小了,因此需要排序的总数据量就变小了,需要的临时文件也相应地变少了。

全字段排序 VS rowid 排序

如果 MySQL 实在是担心排序内存太小,会影响排序效率,才会采用 rowid 排序算法,这样排序过程中一次可以排序更多行,但是需要再回到原表去取数据。

如果 MySQL 认为内存足够大,会优先选择全字段排序,把需要的字段都放到 sort_buffer 中,这样排序后就会直接从内存里面返回查询结果了,不用再回到原表去取数据。

如果内存够,就要多利用内存,尽量减少从盘访问

对于InnoDB来说rowid排序会要求回表多,造成磁盘读,因此不会被优先选择

避免排序的方法

创建联合索引,使查出来的数据是有序的

发布了48 篇原创文章 · 获赞 31 · 访问量 4553

猜你喜欢

转载自blog.csdn.net/qq_39787367/article/details/103861969
今日推荐