1.前言
我们在做Mysql查询的时候,只需要输入一条查询语句,然后返回一个结果,却不知这条语句内部是如何执行的。比如我们就拿下面比较简单的查询语句来举例。
select * from table where id=1;
我因为最近也在准备面试,所以偶然看到了丁奇大佬的45讲的文章,所以对此写一篇博文总结以下。
2.MySQL基本架构
下图是我画出的Mysql的基本架构示意图,从中可以清晰的看出SQL语句在Mysql的各个功能模块中的执行过程。
从上图看出,Mysql可以分为Server层和存储引擎两部分。
Server层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖Mysql的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎层是负责数据的存取和提取的。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。当然InnoDB是最常用的存储引擎。且它从Mysql5.5.5版本就开始成为了默认存储引擎。说完了这些,就看看在Server层中每个组件做了啥吧。
2.1 连接器
连接器顾名思义是用来连接的,在数据库中第一步就是要连接数据库。这个时候连接器就起作用了。他需要负责与你客户端进行建立连接、获取权限、维持和管理连接。连接命令一般是这样写的:
mysql -h$ip -P$port -u$user -p
然后输入完命令后,回车输入密码就可连接了。在连接命令中mysql是客户端工具,是用来跟服务端进行建立连接的,实际上建立连接本质上就是完成TCP握手。接着连接器还要认证你的身份,它则么可能会让你随便连接,这不是坏了规矩。这个时候用的就是你输入的用户名和密码。
如果用户名和密码不对,你就会收到“Access defined for user”的错误,然后客户端程序结束执行。
如果认证通过,别急还没好,连接器就会从权限表中查出你的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。(这边啥意思呢?就是说一个用户成功建立连接后,即使你用管理员账号对这个用户进行权限修改了,也不会影响已经存在连接的权限了。只有重新建立连接才会使用新的权限!!!)。
这个时候总算连接完成了,此时如果你没有任何后续动作,这个连接就是处于空闲状态,你可以使用"show processlist"命令中看到它。
如果客户端长时间没动静,连接器就会自动将它断开,这个时间是由参数wait_timeout控制的,默认值是8小时。
如果在连接被断开之后,如果客户端发送请求的话,就会收到一个错误提醒:"Lost connection to Mysql server during query"。此时如果你想要继续执行命令,那么你就需要重连,然后再次执行。
在这里丁神给我们一个建议
数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。 短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。
建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作, 也就是尽量使用长连接。
但是全部使用长连接后,你可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。
怎么解决这个问题呢?你可以考虑以下两种方案。
1. 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后, 断开连接,之后要查询再重连。
2. 如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权 限验证,但是会将连接恢复到刚刚创建完时的状态。
2.2 查询缓存
这个其实很好理解,无非就是当执行select查询语句的时候。先去缓存中查询一下是不是执行过,如果执行过就直接取缓存中的就可以。其实确实是这样。
Mysql在拿到一个请求后,会先去查询缓存中看看,之前是否执行过这条语句。其内部的数据结构是key-value对的形式,key是查询语句,value是查询结果,如果能够在缓存中找到这个key,则直接返回value给客户端。
那么如果语句不在缓存中呢?那么就会继续执行后面的操作。执行完成后会缓存到查询缓存中。可以看到如果命中率高的话,这个查询缓存效率还是很高的。
但是大多数情况下不介意使用查询缓存,为什么呢?
因为查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的缓存都会被清空。因此你很可能你费劲地把结果缓存起来,还没使用,就被一个更新给全部清空了。对于频繁更新的数据库来说,查询缓存的命中率会非常的低。除非就是你的业务上有一张静态表,很长时间才会更新一次。否则不推荐使用。
好在 MySQL 也提供了这种“按需使用”的方式。你可以将参数 ”query_cache_type” 设置 成 DEMAND,这样对于默认的 SQL 语句都不使用查询缓存。而对于你确定要使用查询缓 存的语句,可以用 SQL_CACHE 显式指定,像下面这个语句一样。
select SQL_CACHE * from table where id=1;
需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻 底没有这个功能了。
2.3 分析器
分析器来了!如果没有命中缓存,他就要到分析器来了,分析器是干啥的呢?有没有想过一个问题,MYSQL是则么去知道你到底去哪个表查询,你执行的啥命令。其实这些都归分析器管。分析器首先会去做“词法分析”。你输入的是由多个字符串和多个空格组成的一条SQL语句,MYSQL需要识别出里面的字符分别是什么,代表什么。
MYSQL从你输入的“select”这个关键字识别出来你是去做查询,然后识别出你的“table”你的表名,接着识别出你的“id”查询的列。
接着他就会进行“语法分析”就是判断你这条sql语句是否满足MYSQL语法。如果你的语句不对就会收到类似“You have an error in your SQL syntax”的错误提醒(PS:我相信大家都收到过)。
2.4 优化器
在没接触到MYSQL底层原理的时候,我早就知道MYSQL内部会自己做优化,但是不知道是谁做,更不知道咋做的。
优化器实际上是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句中有多表关联(join)的时候,决定各个表的连接顺序。比如你执行下面这样的语句,这个语句是执行两个表的join。
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。
这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决 定选择使用哪一个方案。
优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。
2.5 执行器
终于到最后,执行器了,语句终于要执行了!!!
开始执行的时候,首先要判断一个你是否有权限操作这张表table。如果没有的话就会返回没有权限错误。如果有,就会打开表继续执行。比如下面语句
select * from table where id=1
如果此时id没有索引,那么执行器的流程大概时这样的
1.调用InnoDB引擎接口取这个表的第一行,判断id值是否是1,如果不是则跳过,如果是就将这行存储在结果集中。
2.调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
3.执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
至此,这个语句就执行完成了。
3.参考
极客学院Mysql45讲