0%

MySql——排序

全字段排序

  • 创建一个表
1
2
3
4
5
6
7
8
9
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;
  • 查询语句, 使用explain查看语句的执行情况
1
explain select city,name,age from t where city='杭州' order by name limit 1000  ;
  • 可以看到Extra字段,它表示这个语句使用了哪些额外的辅助:
    1. 【Using filesort】 本次查询语句中有order by,且排序依照的字段不在本次使用的索引中,不能自然有序。需要进行额外的排序工作。
    2. 【Using index】 使用了索引
    3. 【Using index condition】 使用了索引下推技术ICP。
    4. 【Using where】 表示本次查询要进行筛选过滤。

img

  • MySql会为每个线程分配一块内存用于排序,称为sort_buffer,一般情况下,这个排序语句的执行流程如下:
    1. 初始化sort_buffer,确认放入name、city、age这三个字段
    2. 从索引city找到第一个满足city=“杭州”条件的主键id。
    3. 到主键索引中取出整行,取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,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。
  • 可以查看number_of_tmp_files参数来查看排序过程中使用的外部临时文件数(由于在外部排序使用的是归并排序,归并排序会把一个大文件分成多个小文件再有序合并,所以需要多个文件)。
  • 排序就分成了两种情况:
    1. 如果 sort_buffer_size 超过了需要排序的数据量的大小,number_of_tmp_files 就是 0,表示排序可以直接在内存中完成。
    2. 否则就需要放在临时文件中排序。sort_buffer_size 越小,需要分成的份数越多,number_of_tmp_files 的值就越大。

rowid排序

  • 如果排序的单行长度太大,sorted_buffer中存储的行数就会很少,这就会导致外部排序需要的临时文件变多,这将会降低排序性能。这时就应该换另一个算法进行排序。
  • 可以设置max_length_for_sort_data的值,它表示用于排序的行数据的长度,如果超出这个值,MySql就会换一个算法进行排序,这时它的执行流程是这样的:
    1. 初始化 sort_buffer,确定放入两个字段,即 name 和 id;
    2. 从索引 city 找到第一个满足 city=’杭州’条件的主键 id
    3. 到主键 id 索引取出整行,取 name、id 这两个字段,存入 sort_buffer 中;
    4. 从索引 city 取下一个记录的主键 id;
    5. 重复步骤 3、4 直到不满足 city=’杭州’条件为止
    6. 对 sort_buffer 中的数据按照字段 name 进行排序;
    7. 遍历排序结果,取前 1000 行,并按照 id 的值回到原表中取出 city、name 和 age 三个字段返回给客户端。

全字段排序 VS rowid 排序

  • 如果 MySQL 实在是担心排序内存太小,会影响排序效率,才会采用 rowid 排序算法,这样排序过程中一次可以排序更多行,但是需要再回到原表去取数据。
  • 如果 MySQL 认为内存足够大,会优先选择全字段排序,把需要的字段都放到 sort_buffer 中,这样排序后就会直接从内存里面返回查询结果了,不用再回到原表去取数据。
  • 这也就体现了 MySQL 的一个设计思想:如果内存够,就要多利用内存,尽量减少磁盘访问。对于 InnoDB 表来说,rowid 排序会要求回表多造成磁盘读,因此不会被优先选择。

创建索引来简化排序

联合索引

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

alter table t add index city_user(city, name);
  • 其实可以创建一个(city、name)的联合索引,当我们通过这个索引找到杭州时,name在city=“杭州”这个子集中就是有序的,整个过程如下:
    1. 从索引 (city,name) 找到第一个满足 city=’杭州’条件的主键 id;
    2. 到主键 id 索引取出整行,取 name、city、age 三个字段的值,作为结果集的一部分直接返回
    3. ;从索引 (city,name) 取下一个记录主键 id;
    4. 重复步骤 2、3,直到查到第 1000 条记录,或者是不满足 city=’杭州’条件时循环结束。
  • 使用索引就不需要进行排序和临时表了,而且只需要扫描前1000行。但是还是需要回表操作

覆盖索引

  • 覆盖索引是指,索引上的信息足够满足查询请求,不需要再回到主键索引上去取数据。
  • 所以针对上面的查询语句,我们创建一个city、name和age的联合索引,对应Sql语句
1
alter table t add index city_user_age(city, name, age);
  • 这时,整个查询过程:
    1. 从索引 (city,name,age) 找到第一个满足 city=’杭州’条件的记录,取出其中的 city、name 和 age 这三个字段的值,作为结果集的一部分直接返回;
    2. 从索引 (city,name,age) 取下一个记录,同样取出这三个字段的值,作为结果集的一部分直接返回;
    3. 重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city=’杭州’条件时循环结束。
-------------本文结束感谢您的阅读-------------