登录 |  注册
首页 >  数据库 · 存储 >  MySql实战精选笔记 >  MySQL order by 是怎么工作的?

MySQL order by 是怎么工作的?

相信很多人在面试过程中,总被问到有没有 SQL 调优经验,对于工作经验年限较少的求职者,通常都是在面试之前从网上百度一些答案,提前背熟,然后面试的时候直接将提前背好的答案说出来。笔者作为一名菜鸟,在刚满一年工作经验的时候,出去面试,就是这么干的。记得去某家公司面试的时候,被面试官问到 order by 在排序的时候执行较慢,这个时候该如何优化?我当初想都没想,就回答说给 order by 子句中的字段加上索引(当然这答案也是我提前从网上百度来的),接着面试官问为什么加索引就能提高 order by 的执行效率的时候,我就懵逼了,这我哪知道为什么啊,百度也没告诉我啊。后来面试自然也就黄了。

现在回想一下,当初是真的菜啊。不过话说回来,为什么给 order by 子句中的字段加索引就能加快 SQL 的执行?它一定能提高 SQL 的效率吗?为了搞清楚这些问题,我们就得从 order by 的实现原理上说起了。

以下面这张表为例,看一下 order by 的工作流程。

mysql1.jpg

city 字段建有索引。

执行:

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

查找 city 为‘杭州’的记录,根据 name 排序。

排序流程

mysql2.jpg

上面查询语句的执行流程:

  1. 初始化 sort_buffer,确定放入 name、city、age 这3个字段。

  2. 从索引 city 中找到第一个满足 city='杭州' 的主键ID(ID_x)。

  3. 到主键索引中找到 ID_x,取出整行,取 name、city、age 3个字段的值,存入 sort_buffer。

  4. 从索引 city 取下一个记录的主键ID。

  5. 重复3、4,直到 city 值不满足条件。

  6. 对 sort_buffer 中的数据按照 name 做快速排序。

  7. 把排序结果中的前1000行返回给客户端。

这个排序过程叫做全字段排序,因为需要返回的字段都放入了 sort_buffer 参与排序过程。

mysql3.jpg

排序可能是在内存中完成,也可能需要外部排序,这取决于排序所需要的内存和 sort_buffer_size 参数值。

sort_buffer_size 是为排序而开辟的内存,如果排序的数据量小于其值,排序就在内存中完成,否则会利用磁盘临时文件来辅助排序。

上面的查询中只返回3个字段,不会太长,可以一起都放在 sort_buffer 中,但如果排序的单行长度太大,MySQL会怎么做?

假设 name、city、age 3个字段定义的总长度为36,而 max_length_for_sort_data = 16,就是单行的长度超了,MySQL认为单行太大,需要换一个算法。

此时,放入 sort_buffer 的字段就会只有要排序的字段 name,和主键 id,那么排序的结果中就少了 city 和 age,需要回表了。

排序流程变为:

  1. 初始化 sort_buffer,确定放入2个字段,name 和 id。

  2. 从索引 city 中找到第一个满足 city='杭州' 的主键ID(ID_x)。

  3. 到主键索引中取出整行,把 name、id 这2个字段放入 sort_buffer。

  4. 从索引 city 取下一个记录的主键ID。

  5. 重复3、4,直到city值不满足条件。

  6. 对 sort_buffer 中的数据按照 name 做快速排序。

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

这种排序成为rowid排序

mysql4.jpg

如果内存够大,优先选择全字段排序,把需要的字段都放到 sort_buffer中,这样排序后就会直接从内存里返回查询结果了,不用回到原表取数据。

MySQL排序优化

建立一个 city、name 的联合索引,使查询 city='杭州' 的结果就是 name 有序的,就不用排序了。

mysql5.jpg

查询过程变为:

  1. 从索引(city,name)找到第一个满足 city='杭州' 的主键ID。

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

  3. 从索引(city,name)取下一个主键ID。

  4. 重复2、3,直到查到1000条,或者不满足条件时结束。

其实,这个查询还可以进一步优化,就是利用覆盖索引。

使用联合索引(city,name)后已经不需要排序过程了,但因为没有 age 字段,所以还需要回表获取。

如果建立一个联合索引(city,name,age),不仅不用排序了,也不用回表取数据了,因为索引中已经包含了查询所需要的字段。

查询过程变为:

  1. 从索引(city,name,age)找到第一个满足 city='杭州' 的记录,取出这3个字段,作为结果集的一部分直接返回。

  2. 从索引取下一个记录,返回。

  3. 重复2,直到查到1000条,或者条件不满足时结束。

小结

上面介绍了排序的工作流程,包括:

  1. 全字段排序

  2. rowid 排序

并进一步思考如何对排序进行优化:

  1. 利用索引使查询结果本身就是有序的。

  2. 如果条件允许,使用覆盖索引,直接返回结果。

上一篇: 尽量使用count(*)统计条数
下一篇: MySQL面试:什么是幻读,幻读有什么影响?
推荐文章
  • mysql只支持一种join算法:Nested-LoopJoin(嵌套循环连接),但Nested-LoopJoin有三种变种:SimpleNested-LoopJoin,IndexNested-LoopJoin,BlockNested-LoopJoin(简单-索引-缓冲区)原理:1.SimpleNe
  • redis是一个内存数据库,一旦断电或服务器进程退出,内存数据库中的数据将全部丢失,所以需要redis持久化 redis持久化就是把数据保存在磁盘上,利用永久性存储介质将数据保存,在特定的时间将保存的数据进行恢复的工作机制redis提供两种持久化机制RDB:存储数据结果,关注点在数据AOF:存储操作
  • 通过SQL的执行过程来介绍MySQL的基础结构.     首先有一个user_info表,表里有一个id字段,执行下面这条查询语句:Select * form user_info where i
  • 索引(Index)是帮助MySQL高效获取数据的数据结构,索引的目的在于提高查询效率,就像字典和书籍的目录一样,有了目录,可以帮助你快速查找你需要的内容。可以理解为一个排好序的快速查找数据结构。也就是
  • 说到数据库事务,大家脑子里一定很容易蹦出一堆事务的相关知识,如事务的ACID特性,隔离级别,解决的问题(脏读,不可重复读,幻读)等等,但是可能很少有人真正的清楚事务的这些特性又是怎么实现的,为什么要有四个隔离级别。今天我们就先来聊聊MySQL中事务的隔离性的实现原理,后续还会继续出文章分析其他特性的
  • 前面我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块。相信你还记得,一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎。那么,一条更新语句
学习大纲