order by 运行过程

应用场景:

很多时候会碰到需要根据指定字段排序来显示结果的需求。下面以市民表为例,简要说明 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;

而你存在这样的一个查询:

select * from t where city = 'Gary Berry' order by name limit 1000;

我们的市民表 t 是存在city 的索引的,下面查看下这个语句的执行计划:

root@172.16:  [test]> explain select * from t where city = 'Gary Berry' order by name limit 1000;
+----+-------------+-------+------+---------------+------+---------+-------+------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref   | rows | Extra                                              |
+----+-------------+-------+------+---------------+------+---------+-------+------+----------------------------------------------------+
|  1 | SIMPLE      | t     | ref  | city          | city | 66      const |   13 | Using index condition; Using where; Using filesort |
+----+-------------+-------+------+---------------+------+---------+-------+------+----------------------------------------------------+

 

Extra 这个字段中的 “ using filesort” 表示的就是需要排序,MySQL会给每一个线程分配一块内存进行排序,称为 sort_buffer。

 

通常这个执行流程如下:

1、初始化sort_buffer,确定放入 name ,city ,age这是三个字段;

2、从索引 city 找到第一个满足 city = ‘Gary Berry’ 条件的主键id;

3、到主键 id索引取出整行,取出 name,city,age三个字段的值,存入sort_buffer 中;

4、从索引 city 取下一个记录的主键 id;

5、重复3、4直到 city 的值不满足查询条件为止;

6、对 sort_buffer 中的数据按照字段 name 做快速排序;

7、按照排序结果取前 1000行返回给客户。

 

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

所以就有了一个情况,当需要返回的字段很多的情况,sort_buffer中存放的字段太多,这时内存里能够存放的行数就会变少,要利用到很多个磁盘临时文件才能完成排序,这样就会严重影响性能~!

 

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

介绍一个 MySQL 的系统参数:

max_length_for_sort_data : 专门控制用于排序的行数据的长度的一个参数。如果单行的长度过大,MySQL就会选择新的算法(rowid 排序算法),不放入太多的字段到内存。

下面我修改max_length_for_sort_data ,让MySQL 采用新的算法:

SET max_length_for_sort_data = 16;

city、name、age 这三个字段的定义总长度是 36 ,max_length_for_sort_data 设置为 16;

整个的执行过程将会变成如下这个样子:

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、对 sort_buffer 中的数据按照字段 name 进行排序;

7、遍历排序结果,取前 1000 行,并按照 id 的值回到原表中取出 city、name 和 age 三个字段返回给客户端。

 

可以看出两种算法,第二种多访问了一次 t 表主键索引,会额外的造成磁盘读。

而MySQl 做排序是一个成本很高的操作,那有什么好的方法数据库可以不需要创建临时表,也不需要排序?

可以设想如果能保证从city这个索引取出的行,天然的就是按照name递增排序的话,是不是就可以不用再排序了呢?

root@172.16:  [test]> alter table t add index idx_city_name(city,name);
 
root@172.16:  [test]> explain select * from t where city = 'Gary Berry' order by name limit 1000;
+----+-------------+-------+------+--------------------+---------------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys      | key           | key_len | ref   | rows | Extra       |
+----+-------------+-------+------+--------------------+---------------+---------+-------+------+-------------+
|  1 | SIMPLE      | t     | ref  | city,idx_city_name | idx_city_name | 66      const |   13 | Using where |
+----+-------------+-------+------+--------------------+---------------+---------+-------+------+-------------+
1 row in set (0.00 sec)

确实是这样的,并且这个查询的整个过程将变成如下方式:

1、从索引(idx_city_name)中找到 第一个满足 city = 'Gary Berry' 条件的主键id;

2、到主键 id 索引取出整行,取name,city,age 三个字段的值,作为结果集的一部分直接返回;

3、从索引(idx_city_name)取出下一个记录主键id;

4、重复步骤2、3,直到查到第 1000条记录,或者是不满足city = ‘Gary Berry’条件时循环结束。

 

上面给大家总结的是MySQL里面order by语句的几种算法流程。

开发系统过程中,总是不可避免的使用到 order by 语句,心里要清楚每个语句排序逻辑是怎么实现的,才能够分析出在最坏情况下,每个语句的执行对系统资源的消耗。

 

如果提高order by的速度:

1、order by 时 select * 是一个大忌(避免使用),order by的字段存在合适索引;

2、尝试提高 sort_buffer_size 但是需要根据系统能力提高,这个参数是针对每个thread的,max_length_for_sort_data 的提高要根据 sort_buffer_size 的大小合理设定。

 

下面列举几个order by 能用到索引以及order by 不能使用索引排序的情景:

key a_b_c(a,b,c)

order by 能使用索引最左前缀

-- order by a

-- order by a,b

-- order by a,b,c

-- order by a desc,b desc,c desc

如果 where 使用索引的最左前缀定义为常量,则order by能使用索引

-- where a = const order by b,c

-- where a = const and b =const order by c

-- where a = const order by b,c

-- where a = const and b > const order by b,c

不能使用索引进行排序

-- order by asc,b,desc,c,desc      /*排序不一致*/

-- where g = const order by b,c          /*丢失 a 索引*/

-- where a = const order by c               /*丢失b 索引*/

-- where a = const order by a,d         /* d 不是索引的一部分 */

-- where a in (...) order by b,c              /* 对于排序来说,多个相等条件也是范围查询*/

 

posted on 2020-04-21 18:21  狂奔中社会主义酸菜  阅读(304)  评论(0编辑  收藏  举报

导航