MySQL基础架构
首先MySQL的基础架构图如下图所示
大体来说MySQL可以分为Server层和存储引擎层两部分。Server层包括连接器,查询缓存,分析器,优化器,执行器等,蕴含MySQL大多数核心服务功能,以及所有的内置函数,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等等。
下面从上到下介绍每一个组件的作用。
连接器
我们如果想操作MySQL数据库,那么我们所操作的客户端首先需要去连接MySQL的服务器。这时,服务器上的连接器就是负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般这么写:
mysql -h$ip -P$port -u$user -p
输入完命令之后,就需要在交互对话里面输入密码。连接命令中的mysql是客户端工具,用来跟服务端建立连接,在完成经典的TCP握手后,连接器就要开始认证我们的身份,这个时候有两种结果
- 如果用户名和密码不对,我们就会收到一个"Access denied for user" 的错误,然后客户端程序结束执行。
- 如果用户名密码验证通过,连接器就会到权限表里面查询我们拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。(这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有在新建的连接才会使用新的权限设置。)
连接完成后,如果没有后续动作,这个连接就处于空闲状态,我们可以是使用show processlist命令中看到他。客户端如果很长时间都处于Sleep状态,那么连接器就会将他断开,这个时间是由参数wait_timeout控制的。默认值是8小时。
如果在连接断开之后,客户端再吃发送请求的话,就会收到一个错误提醒:Lost connection to MySQL server dring query。这时候入伙我们要继续,就会进行重连,然后在执行请求了。
数据库中,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短链接则是指每次执行完很少的几次查询就断开连接,下次查询在重新建立一个。
因为连接的过程是比较复杂的,所以在使用中尽量使用长连接。但是全部使用长连接后,又是MySQL占用内存增长的特别快,这是因为MySQL在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。如果长连接累计下来,可能导致内存占用太大,被系统强行杀掉(Out of Memory)。
解决这个问题有以下两种方案。
- 定期断开长连接,使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,段考链接,之后要查询在重连。
- 如果使用的是MySql5.7或者更新版本,可以在每次执行一个比较大的操作后,通过执行mysql_reset_connection来初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到创建完成的状态。
查询缓存
连接建立完成后,我们就可以执行select语句了,执行逻辑会来到第二部:查询缓存。查询缓存是使用KV键值对的形式,直接缓存在内存中。key是查询语句,value是查询的结果。这里作者建议不使用查询缓存,因为查询缓存的失效非常频繁,只要有一个表发生更新操作,这个表上的查询缓存就会被清空。
在MySQL8.0之前MySQL提供了按需使用的方式,将query_cache_type设置成DEMAND,这样对于默认的SQL语句不适用查询缓存,而对于使用查询缓存的语句,可以使用SQL_CACHE显示指定
select SQL_CACHE * from T where ID=10;
在MySQL8.0之后,查询缓存没有了。
分析器
如果没有命中查询缓存,就开始执行语句了。首先,MySQL需要知道我们要做什么,因此需要对SQL语句做解析。
分析器首先会做词法分析,我们输入的是由多个字符串和空格组成的一条SQL语句,MySQL需要识别出里面的字符串分别是什么,代表什么。
MySQL从select中识别出来,这是一个查询语句,他也要把字符串T识别成表名T,把字符串ID识别成列ID。做完了这些识别后,就要做语法分析。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个SQL语句是否满足MySQL语法。如果语句不对,就会收到You have an error in your SQL syntax 的错误提醒。
优化器
经过了分析器,MySQL就知道我们要做什么,但是在开始执行之前,还要先经过优化器的处理。优化器是在表里面有多个索引的时候,决定使用哪个索引,或者在一个语句有夺标关联的时候,决定各个表的连接顺序,比如我们执行下面语句
select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
- 可以先从表t1里面取出c=10的记录的ID值,在根据ID值关联到t2,在判断t2里面d的值是否等于20。
- 可以先从表t2里面去除d=20的记录的ID值,在根据ID值关联到t1,在判断t1里面c的值是否等于10。
这两种执行方法的逻辑结果是一样的,但是效率不一样,所以优化器自己回去判断选择使用哪一种方案。
执行器
MySQL通过分析器知道了我们要做什么,通过优化器知道了该怎么去做,于是就进入了执行器阶段,开始执行语句。
开始执行的时候,会实现判断以下我们对这个表有没有执行查询的权限,如果没有,就会返回没有权限的错误,如果有权限,就会打开表继续执行,执行器会根据表的引擎定义,去使用这个引擎提供的接口。
若ID没有索引,那么执行器的执行流程是这样的:
- 调用InnoDB引擎接口取这个表的第一行,判断ID的值是不是10,如果不是则跳过,如果是则将这行数据存储在结果集中;
- 调用引擎接口取"下一行",重复相同的逻辑判断,直到取这个表的最后一行。
- 执行器将上述遍历过程中所有满足条件的行组成的记录及作为结果集返回给客户端。
至此,结束。
对于有索引的表,执行逻辑也大体相同。
在慢查询日志中会看到一个rows_examined的字段,表示这个语句执行过程中扫描了多少行,这个值就是在执行器每次调用引擎获取数据行的时候累加的。在某些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟row_examined并不是完全相同的。