更好理解ORDER BY内部排序和性能优化-mysql

5 阅读4分钟
create table a(
    city varchar(16) not null,
    name varchar(16) not null,
    id int(11) not null,
    primary key(id)
)

一、全字段排序

比如在执行一个

select city, name, ID from T 
where city='广州' 
order by name limit 200;
  • city 字段有索引(用来快速过滤出 “广州” 的记录)
  • name 字段没有索引(无法直接按 name 有序返回)

    触发全字段排序,mysql就会把需要返回的字段(city,name,UID)放到一个 sort-buffer的内存,按这个内存块的name排序。

流程

1、先利用索引过滤

先通过索引 city,找到city= 杭州对应的主键ID。

2、根据主键回表取返回字段

根据主键ID,读取整行数据,只取需要返回的字段city,name,id,

放入内存块sort-buffer

3、在sort-buffer排序

sort_buffer 是 MySQL 为每个线程分配的一块排序专用内存

如果 sort_buffer 能装下所有待排序数据就直接排序。

如果数据太多,就会把数据分成多份,每份排序后存到临时磁盘文件中,把这些有序文件合并成一个大的有序结果

4、返回结果

二、rowid排序

触发条件:当单行数据长度超过 max_length_for_sort_data 参数时,MySQL 会认为单行太大,不适合把所有返回字段都塞进 sort_buffer,而改用 rowid 排序

注:max_length_for_sort_data是用户自己设置。

set max_length_for_sort_data = 20;

流程

把需要排序的字段(如name)和主键ID放入sort-buffer

排序完成后,再根据排序后的ID回表提取其他需要返回的字段(city,name,id)。

三、为什么需要排序?

在只有 city 索引的情况下:

  • city 索引只保证了 city 字段的有序性,但满足 city='杭州' 的行,name 字段是无序的。
  • 因此 order by name 必须通过排序(全字段或 rowid)来实现,

四、全字段排序 vs rowid 排序

全字段排序

原理(简):把需要返回的字段在sort-buffer排序。

pre:无需回表,一次读取

pro: 单行数据大时,sort_buffer 很快占满,需依赖磁盘临时文件,性能差

rowid排序

原理(简):只把order by的字段和主键ID在sort-buffer排序,再回表提取其他返回字段。

pre:内存占用小,减少临时文件数量

pro: 多一次回表操作,增加 IO 开销

需要采用全字段排序和rowid排序是因为原数据是无序的。

五、联合索引

如果在city索引取的行是按照name递增排序,就不用做排序了(全字段排序ORrowid排序)。

所以可以在创建表后,表外添加索引,创建一个city和name的联合索引,

alter table a add index city_user(city,name);

当我们创建 (city, name) 联合索引后: 索引的结构是先按 city 排序,再按 name 排序。

———— city='广州' 的行,因为name 天然是有序的。 所以此时 order by name 不再需要排序,直接按索引顺序读取即可,性能大幅提升。

流程

(city, name) 索引找到第一个 city='广州' 的行,获取主键 id

回表到主键索引,取出 id 字段,与索引中的 city, name 一起返回。

按索引顺序取下一行,重复步骤 2,直到取够 1000 行。

六、覆盖索引

如果我们进一步创建 (city, name, di) 覆盖索引:

这样的话,索引中就包含了查询的所有需要返回的字段,就不用回表操作,直接读取索引数据。

输出explain 结果中的 Extra 字段显示 Using index,是覆盖索引的标志,表示性能达到最优。

alter table a add index city_user(city,name,id);

流程

  1. (city, name, age) 索引找到第一个 city='杭州' 的行,直接取出 city, name, age 返回。
  2. 按索引顺序取下一行,重复步骤 1,直到取够 1000 行。

结论

排序是成本很高的操作,所以能通过索引避免就尽量避免。

覆盖索引是性能优化的最优手段,但需要权衡索引的维护成本,和查询收益,不是所有场景都适合。