全字段排序
- 创建一个表
1 | CREATE TABLE `t` ( |
- 查询语句, 使用explain查看语句的执行情况
1 | explain select city,name,age from t where city='杭州' order by name limit 1000 ; |
- 可以看到Extra字段,它表示这个语句使用了哪些额外的辅助:
- 【Using filesort】 本次查询语句中有order by,且排序依照的字段不在本次使用的索引中,不能自然有序。需要进行额外的排序工作。
- 【Using index】 使用了索引
- 【Using index condition】 使用了索引下推技术ICP。
- 【Using where】 表示本次查询要进行筛选过滤。
- MySql会为每个线程分配一块内存用于排序,称为sort_buffer,一般情况下,这个排序语句的执行流程如下:
- 初始化sort_buffer,确认放入name、city、age这三个字段
- 从索引city找到第一个满足city=“杭州”条件的主键id。
- 到主键索引中取出整行,取name、city、age这三个字段,存入到sort_buffer中
- 从索引city取下一个记录的主键id
- 重复3、4步骤指定city的值不满足查询条件为止。
- 对sort_buffer中的数据按照name做快速排序
- 按照排序结构取前1000行返回给客户端
- sort_buffer_size,就是 MySQL 为排序开辟的内存(sort_buffer)的大小。如果要排序的数据量小于 sort_buffer_size,排序就在内存中完成。但如果排序数据量太大,内存放不下,则不得不利用磁盘临时文件辅助排序。
- 可以查看number_of_tmp_files参数来查看排序过程中使用的外部临时文件数(由于在外部排序使用的是归并排序,归并排序会把一个大文件分成多个小文件再有序合并,所以需要多个文件)。
- 排序就分成了两种情况:
- 如果 sort_buffer_size 超过了需要排序的数据量的大小,number_of_tmp_files 就是 0,表示排序可以直接在内存中完成。
- 否则就需要放在临时文件中排序。sort_buffer_size 越小,需要分成的份数越多,number_of_tmp_files 的值就越大。
rowid排序
- 如果排序的单行长度太大,sorted_buffer中存储的行数就会很少,这就会导致外部排序需要的临时文件变多,这将会降低排序性能。这时就应该换另一个算法进行排序。
- 可以设置max_length_for_sort_data的值,它表示用于排序的行数据的长度,如果超出这个值,MySql就会换一个算法进行排序,这时它的执行流程是这样的:
- 初始化 sort_buffer,确定放入两个字段,即 name 和 id;
- 从索引 city 找到第一个满足 city=’杭州’条件的主键 id
- 到主键 id 索引取出整行,取 name、id 这两个字段,存入 sort_buffer 中;
- 从索引 city 取下一个记录的主键 id;
- 重复步骤 3、4 直到不满足 city=’杭州’条件为止
- 对 sort_buffer 中的数据按照字段 name 进行排序;
- 遍历排序结果,取前 1000 行,并按照 id 的值回到原表中取出 city、name 和 age 三个字段返回给客户端。
全字段排序 VS rowid 排序
- 如果 MySQL 实在是担心排序内存太小,会影响排序效率,才会采用 rowid 排序算法,这样排序过程中一次可以排序更多行,但是需要再回到原表去取数据。
- 如果 MySQL 认为内存足够大,会优先选择全字段排序,把需要的字段都放到 sort_buffer 中,这样排序后就会直接从内存里面返回查询结果了,不用再回到原表去取数据。
- 这也就体现了 MySQL 的一个设计思想:如果内存够,就要多利用内存,尽量减少磁盘访问。对于 InnoDB 表来说,rowid 排序会要求回表多造成磁盘读,因此不会被优先选择。
创建索引来简化排序
联合索引
1 | select city,name,age from t where city='杭州' order by name limit 1000 ; |
- 其实可以创建一个(city、name)的联合索引,当我们通过这个索引找到杭州时,name在city=“杭州”这个子集中就是有序的,整个过程如下:
- 从索引 (city,name) 找到第一个满足 city=’杭州’条件的主键 id;
- 到主键 id 索引取出整行,取 name、city、age 三个字段的值,作为结果集的一部分直接返回
- ;从索引 (city,name) 取下一个记录主键 id;
- 重复步骤 2、3,直到查到第 1000 条记录,或者是不满足 city=’杭州’条件时循环结束。
- 使用索引就不需要进行排序和临时表了,而且只需要扫描前1000行。但是还是需要回表操作
覆盖索引
- 覆盖索引是指,索引上的信息足够满足查询请求,不需要再回到主键索引上去取数据。
- 所以针对上面的查询语句,我们创建一个city、name和age的联合索引,对应Sql语句
1 | alter table t add index city_user_age(city, name, age); |
- 这时,整个查询过程:
- 从索引 (city,name,age) 找到第一个满足 city=’杭州’条件的记录,取出其中的 city、name 和 age 这三个字段的值,作为结果集的一部分直接返回;
- 从索引 (city,name,age) 取下一个记录,同样取出这三个字段的值,作为结果集的一部分直接返回;
- 重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city=’杭州’条件时循环结束。