16 | “order by”是怎么工作的?

字段排序显示。市民表,城市是“杭州”,姓名排序返回前 1000 姓名、年龄。

16 | “order by”是怎么工作的?_第1张图片

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

一、全字段排序

避免全表扫描, city 加上索引。explain :

图 1 使用 explain 命令查看语句的执行情况

Extra 的“Using filesort”表示需要排序,给每个线程分配一块内存用于排序,sort_buffer

16 | “order by”是怎么工作的?_第2张图片
图 2 city 字段的索引示意图  

city='杭州’,从 ID_X 到 ID_(X+N) 。执行流程(全字段排序):

1.  初始化 sort_buffer,放入 name、city、age ;

2.  从索引 city 满足 city='杭州’条件主键 id, ID_X;

3.  到主键 id 索引取出整行,取 name、city、age 存入 sort_buffer ;

4.  重复2,从索引 city 取下一个记录的主键 id;

5.  重复步骤 3、4 直到 不满足, ID_Y;

6.  对 sort_buffer 按照字段 name 做快速排序

7.  取前 1000 行返回给客户端。

16 | “order by”是怎么工作的?_第3张图片
图 3 全字段排序

排序在内存/外部,取决所需的内存和参数sort_buffer_size:为排序开辟的内存(sort_buffer)的大小。内存放不下,利用磁盘临时文件辅助

确定排序语句是否用临时文件:

1.只对本线程有效:SET  optimizer_trace='enabled=on';

2.保存 Innodb_rows_read 初始值:select  VARIABLE_VALUE into @a from performance_schema.session_status where variable_name =  'Innodb_rows_read';

执行语句:select city, name,age from t where city='杭州'  order by name limit 1000;

3.查看 OPTIMIZER_TRACE 输出:SELECT * FROM  `information_schema`.`OPTIMIZER_TRACE`\G

4.保存 Innodb_rows_read 当前值:select  VARIABLE_VALUE into @b from performance_schema.session_status where  variable_name = 'Innodb_rows_read';

5.计算 Innodb_rows_read 差值:select @b-@a;

OPTIMIZER_TRACE 结果来确认:

16 | “order by”是怎么工作的?_第4张图片
图 4 全排序的 OPTIMIZER_TRACE 部分结果  

number_of_tmp_files 排序过程临时文件数。为什么需要 12 个文件?内存放不下时,放临时文件中(归并排序)将需要排序的数据分成 12 份,每一份单独排序后存在这些临时文件中。再合并成有序大文件。number_of_tmp_files =0,可直接内存中完成

1.sort_buffer_size越小,份数越多,number_of_tmp_files 越大。

2.examined_rows=4000:参与排序行数

3.sort_mode 的packed_additional_fields:排序过程对字符串做“紧凑”处理。即使定义varchar(16),排序过程还是按实际长度分配空间

4.select @b-@a的返回结果是 4000

避免干扰结论,internal_tmp_disk_storage_engine 设置成 MyISAM。否则为 4001

查OPTIMIZER_TRACE 时,用临时表,默认 InnoDB。从临时表取出来的时候,会让Innodb_rows_read值加 1。

二、rowid 排序(InnoDB 不优先选择)

上面只对原表读一遍,剩下sort_buffer 和临时文件执行。问题:返回字段多,sort_buffer字段数多,内存行数少,临时文件多,排序性能差。

单行大,效率不好。MySQL 另外一种算法:

SET  max_length_for_sort_data = 16;

MySQL 用于排序的行数据的长度的一个参数。单行长度超过这个值,MySQL 就认为单行太大换个算法

city、name、age 总长度 36,新的算法放入 sort_buffer 的字段,只要排序列( name 字段)和主键 id。

少了 city 和 age ,不能直接返回,流程:

1.  初始化 sort_buffer,放入name 和 id

2.  索引 city 找第一个city='杭州’ 主键 id, ID_X

3.  主键 id 索引取整行,name、id 存入 sort_buffer 中;

4.  重复2,索引 city 取下一个记录主键 id;

5.  重复3、4 直到不满足 city='杭州’, ID_Y;

6. 对 sort_buffer 中 name 排序;

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

16 | “order by”是怎么工作的?_第5张图片
图 5 rowid 排序

rowid 排序多访问了一次表 t 的主键索引步骤 7

id到原表查到 city、name 和 age 结果,不在服务端耗费内存存储结果,直接返回给客户端。

排序数据4000 。但select @b-@a变成 5000 。除排序外,去原表取值。limit 1000,多读 1000 行。

16 | “order by”是怎么工作的?_第6张图片
图 6 rowid 排序的 OPTIMIZER_TRACE 部分输出  

sort_mode 变成 rowid>,参与排序的只有 name 和id 

number_of_tmp_files 变 10 ,每行变小,总数据量小,临时文件也少。

三、全字段排序 VS rowid 排序

内存小影响排序效率,用 rowid 排序算法,可以排序更多行,再回到原表取数据。

内存大,全字段排序,都放sort_buffer 中,不用再回到原表取数据。

如果内存够,多利用,尽量减少磁盘访问。

天然递增排序,不用再排序:

创建 city 和 name 的联合索引,对应SQL 语句:

alter table t add  index city_user(city, name)

16 | “order by”是怎么工作的?_第7张图片
图 7 city 和 name 联合索引示意图

只要 city =杭州name 一定是有序的。查询过程的流程:

1.  从索引 (city,name) 到第一个满足 city='杭州’条件的主键 id

2.  到主键 id 索引取出整行,取 name、city、age 三个字段的值,直接返回;

3.  重复2.从索引 (city,name) 取下一个记录主键 id;

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

16 | “order by”是怎么工作的?_第8张图片
图 8 引入 (city,name) 联合索引后,查询语句的执行计划

不需要临时表和排序。

图 9 引入 (city,name) 联合索引后,查询语句的执行计划  

Extra 字段中没有 Using filesort 了,也就是不需要排序了。而且由于 (city,name) 这个联合索引本身有序,找到满足条件前 1000 条记录退出。

覆盖索引:索引信息满足查询,不需回到主键索引上去取数据

创建一个 city、name 和 age 联合索引:alter table t add  index city_user_age(city, name, age);city 相同行,name 递增排序

1.  从索引 (city,name,age) 找到第一个满足 city='杭州’条件的记录,取出其中city、name 和 age 这三个字段的值,直接返回;

2.  从索引 (city,name,age) 取下一个记录,直接返回;

3.  重复执行步骤 2,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。

16 | “order by”是怎么工作的?_第9张图片
图 10 引入 (city,name,age) 联合索引后,查询语句的执行流程  
图 11 引入 (city,name,age) 联合索引后,查询语句的执行计划

Extra里面多了“Using index”,用覆盖索引,性能快

不是每个查询能用上覆盖索引,索引要维护。需要权衡。

小结

 order by 种算法流程。

清楚每个语句排序逻辑怎么实现,分析出最坏情况下,每个语句执行对系统资源消耗,不犯低级错误。

思考题

city_name(city, name)联合索引,查两个城市中市民姓名:

mysql> select  * from t where city in ('杭州',"  苏州") order by  name limit 100;

1.执行有排序过程吗?

2.不需要排序方案,怎么实现呢?

3.分页需求,显示101 页, “limit 10000,100”, 实现方法?

答:

1.不需排序

2. 执行 select * from t where city=“苏州” order by name limit 100;存进内存数组 B。

3. A 和 B 两个有序数组,归并排序,name 最小前 100 值,需要结果。

select * from t where city=" 杭州" order by name limit 10100;和

select * from t where city="苏州 " order by name limit 10100。

数据量大,两个连接同时一行行读结果,归并排序拿到两个结果集,按顺序取第 10001~10100 的 name 值,就是需要的结果了。

明显损失,返回数据量大。改成:select id,name。。。

归并排序得按 name 顺序第 10001~10100 的 name、id 的值拿这100 个 id查出所有记录。

根据性能需求、复杂度做权衡。

评论1

select * from t order by create_time desc ;优化器会根据 order by create_time 选择使用 create_time 索引么 

误以为优化器根据 where 后字段条件选索引 ,没有where 时以为不会走索引 。

本地建表加20w数据 ,explain发现走全表没有走索引analyze table重新统计 ,再次查询果走索引 

where和 order共同影响,加联合索引后,语句执行逻辑

评论2

场景 ,插入时间倒序查看 ,除分页 , 没where 的条件 ,只有主键索引 。 

给 create_time 创建索引,实际没用 。 本身没用到 create_time 索引,增加维护成本

1. 初始化 sort_buffer 内存

2. 没索引 ,扫出全表数据到 sort_buffer 中

2. 如果内存够则直接内存按时间排序 

3. 如果内存不够则按数据量分成不同文件分别按时间排序后整合

4. 根据数量分页查询数量 回聚集索引中用 ID 查询数据

5. 返回

问题一 :无条件列,除了全表扫,建立索引办法

问题二 :  加入 group by , 数据如何走

问题三 :bigInt(20) 、 tinyint(2) 、varchar(32) 带数字与不带数字有何区别?

问题一 :

1)全表扫描,两种选择一排序。二级索引,再回表成本比全表扫描排序更高。

2)无条件查询 limit m.m值小,二级索引.

优化器根据索引有序性去回表查,得到m条数据,终止循环,比全表扫描小,走

问题二:

group by a,a上用索引,rowid排序

group by limit,不能用索引,堆排序

group by a,a上有索引,根据选取值不同,索引扫描方式不同

select * from t group by a 索引全扫描,为什么选择走索引全扫描

select a from t group by a 索引松散扫描,不用扫描每行值

问题三:都不影响能存储值。

ps:长连接,sql申请sort_buffer_size会话级别内存,sql成功执行完,连接sleep状态。占用的内存空间不会释放?  内存排序后free掉还给系统

你可能感兴趣的:(16 | “order by”是怎么工作的?)