前言
我们学习时,经常听老师说学一门课程,我们要把教材的目录翻一下,了解一下概要,不要一头扎到细节里。学技术也一样,当我们学习一个系统时,应该先鸟瞰其全貌,了解其组件及其作用,再做具体的学习。
解析
select * from T where id =1;
上面这条sql我相信大家都理解它的意思。但是它是怎么执行的呢?下面会通过对Mysql各个组件的讲解介绍这个过程。
Mysql鸟瞰图如下:
Mysql分为两大部分:Server 层和存储引擎。
Server层包括连接器,查询缓存,分析器,优化器和执行器。Server层都是通用的组件,存储引擎有多种实现可供我们选择。Mysql 5.5之后默认存储引擎就是innodb。存储引擎介绍
互联网大部分业务都是OLTP类型,innodb能满足大部分的互联网业务,这也是Mysql把innodb存储引擎作为默认的原因。
连接器
连接器管理着连接的创建,维持和管理以及权限验证。
执行sql之前,我们需要先和Mysql服务器建立连接。通过Mysql自带的客户端连接Mysql,需要如下命令:
mysql -h$ip -P$port -u$user -p
如果输入的ip和port没有问题,Client会和Mysql进行经典的TCP三次握手。接着会进行权限验证:
- 如果用户名或密码不对,你就会收到一个"Access denied for user"的错误,然后客户端程序结束执行。
- 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。
这就意味着权限的验证是在连接建立时进行的,之后即使你修改该用户的权限,也不会起作用。新建立的连接才会使用新的权限。
客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。
如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。
数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。
创建连接的过程是比较复杂的,所以一般建议减少连接创建的次数,尽量使用长连接。
但是全部使用长连接后,你可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。
对于这种问题,DBA可能会建议你两种方式处理:
- 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
- 如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。
查询缓存
通过权限验证后,可以执行select语句了,首先会来到查询缓存。
查询缓存是key-value形式存储的,key是查询语句,value是查询结果。如果能命中,查询语句直接返回,否则会进行后面的步骤,然后将查询结果放到查询缓存中,再将查询结果返回给客户端。能够看出如果能命中的话效率还是很高的。
8.0已经将这个模块删除掉,事实上我也不建议读者使用查询缓存。因为查询缓存很容易失效,结果涉及到的表如果有任何的更新缓存就会失效。可以想想辛辛苦苦创建的一大堆缓存,因为一条写语句就失效了,这对大多数应用都得不偿失。
分析器
如果没有命中查询缓存,就开始真正的处理sql了。首先Mysql要知道你要做什么。分析器会分析你发送的一大串字符串是什么意思,也就是词法分析。比如会根据select判断出你要执行查询语句,识别出表名T,字段名id等等。此外,分析器还会校验你的sql是否符合语法。如果语法不对,比如说select少写了个s,你会收到类似如下错误:You have an error in your SQL syntax...... .
这儿会校验表名和字段名吗?答案是会的。表的元数据信息server层是知道的,不用像具体数据一样需要去存储引擎取才知道。
优化器
Mysql知道我们要干什么了,接下来要考虑怎么去执行我们的sql,包括索引的选择,join的话还会涉及到基准表的确定。经过了优化器,sql的执行计划就确定下来了。
执行器
接下来,开始真正的执行sql。
开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限)。有人可能会有疑问,这一步的权限校验不能放到前面吗?
答案是不能的,有些时候,SQL语句要操作的表不只是SQL字面上那些。比如如果有个触发器,得在执行器阶段(过程中)才能确定。优化器阶段前是无能为力的。
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:
- 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
- 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
- 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
至此,这个语句就执行完成了。
对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。可以看出如果数据行数比较多的话,走索引是比全表扫描要快很多的。
你会在数据库的慢查询日志中看到一个 rows_examined 的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。
有时候执行器调用一次,存储引擎会扫描了多行。因此引擎扫描行数和rows_examined并不是完全相同的。后面我会专门写篇文章介绍存储引擎执行规则。
到此,一条查询sql执行完成了。