假设咱们要查询一个市民表中城市=杭州的全部人的名字,而且按照名字排序mysql
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语句能够这样写sql
select city,name,age from t where city='杭州' order by name limit 1000 ;
接下来咱们看下explain的结果性能
图中的Extra这一列下面的Using filesort表示须要排序,MySQL会为每一个链接分配一块内存用于排序,就是sort_buffer,sort_buffer_size能够调整该排序内存大小优化
由于咱们where条件用到了city,因此咱们在city上面创建了索引spa
咱们先看下该索引结构设计
从图中能够看出知足city=杭州的条件是ID_X到ID_Y之间的数据3d
一般状况下这个语句的执行流程以下:code
1.初始化sort_buffer,肯定放入name,age,city三个字段blog
2.从索引city中找到第一个符合条件的数据,也就是ID_X这个排序
3.取出索引中id的值,回表查询name,age,city的数据放入sort_buffer中
4.从索引city取下一个符合条件的id
5.重复步骤3,4直到city的值不知足city=杭州的条件,也就是图中ID_Y
6.对sort_buffer中的数据按照name排序
7.按照排序结果取前1000行数据返回给客户端
咱们把这个排序过程叫全字段排序
以下图所示
上图按name排序这个动做可能在内存中完成也可能须要外部排序,这取决于排序须要的内存大小和sort_buffer_size这个参数
若是排序须要的内存大于sort_buffer_size设置的数值,那么就须要使用磁盘临时文件辅助排序
rowid排序
在上面的那个全字段排序中,只对原表查询了一次,可是若是查询的字段不少的话,那么sort_buffer中就会不少数据,就会使用到
磁盘临时辅助文件排序,这样性能会变差。
那么若是mysql认为单行数据过大会怎么办呢?
接下来设置一下这个参数为16
max_length_for_sort_data这个参数是mysql专门用来控制用于排序的行数据的单行的长度的一个参数,若是单行数据的字段的长度超过这个参数设置的值
那么就会使用rowid排序,好比说咱们这个例子中name,age,city这三个字段的单行数据长度之和要是大于16,那么就会使用rowid排序
排序流程:
1.初始化sort_buffer,肯定放入id,name
2.取出city索引中第一个知足条件的索引的id值
3.到主键id索引里面取出整行,取出name,id字段放入sort_buffer
4.去下一个符合条件的索引记录,放入sort_buffer中
5.重复步骤3.4直到不知足city=杭州
6.对sort_buffer中的数据按照name进行排序
7.遍历排序结果取出前1000行的数据的id,去表中查询出name,age,city返回给客户端
能够看出来rowid排序比全字段排序多了一次表查询就是步骤7
咱们来对比下这两个排序
若是mysql以为内存不够用就会用到rowid排序,若是内存够用则用全字段排序
也就是说Mysql有个设计思想,就是若是内存够,就尽可能用内存,尽可能减小磁盘的访问
看到这里你是否是以为Mysql排序是一个很是复杂的流程,性能会很差,那么是否是全部的order_by语句都要排序呢?
不是的,若是须要排序的字段自然就是有序的,那么就不须要排序,啥意思呢,好比说咱们创建一个city和name的联合索引
alter table t add index city_user(city, name);
做为与city索引的对比,咱们看看这个索引
若是创建了这个索引那么执行流程就变成了这样
1.查询出第一条联合索引中city,name里面city=杭州的数据的id值
2.到主键索引里面取出整行,取出name,age,city字段
3.从索引city,name去下一个记录主键id
4.重复步骤2,3直到查到1000条记录或者不符合city=杭州循环结束
能够看到这个过程不须要排序,也不须要用到临时表
用explain验证一下
那么这个语句还有没有优化空间呢?
有的
咱们创建一个三个字段的联合索引
alter table t add index city_user_age(city, name, age);
那么流程就变成了这样
1.查询出索引中第一条符合条件的数据,取出city,name,age做为结果集的一部分直接返回
2.从索引继续取下一个符合条件的数据做为结果集的一部分直接返回
3.重复步骤2直到查到1000条记录或者不符合city=杭州循环结束
这里其实就是用到了覆盖索引,直接不用回表查询了
固然这里绝对不是说遇到问题就加索引,这里只是举个例子,由于毕竟维护索引也是有代价的
了解更多:https://www.toutiao.com/c/user/83293539887/#mid=1633933053814798