
MySQL的基础架构以及一条查询sql语句的执行流程
详细介绍了MySQL的基础架构以及一条查询sql的执行流程。
如果不想作为一个只能简单的写写sql工程师,而是想要深入的学习MySQL,那么我们有必要首先从宏观的角度来了解MySQL的整体架构,只有把握住了整体,才能深入细节。
面试中高级工程师的时候,常常被问到:一条sql语句在mysql中如何执行的?在学习了本文之后,你将会得到答案。
文章目录- 1 Mysql整体架构
- 2 连接器
- 2 查询缓存
- 4 分析器
- 5 优化器
- 5.1 索引选择的逻辑
- 6 执行器
- 7 一条查询sql的执行流程
MySQL的简略整体架构图如下:
根据上图,Mysql架构可以简单分为Server层和存储引擎层:
下面依次简单介绍每一个组件!
2 连接器连接器主要用于连接处理、授权认证、安全相等相关功能。
如果采用连接命令,则如下:mysql -h$ip -P$port -u$user -p。
当客户端通过TCP三次握手连接到MySQL服务器时,服务器对首先其进行身份认证,可以通过用户名和密码的方式进行认证,也可以通过SSL证书进行认证。登录认证通过后,服务器还会查询出当前用户拥有的权限,之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。
每个客户端的连接都对应着服务器上的一个线程。服务器的连接器上维护了一个线程池,避免为每个连接都创建销毁一个线程。
如果连接完成后,未来的一段时间里没做任何操作,这个连接就处于空闲的状态,通过show processlist命令中看到空闲连接列表,如下所示,Sleep标志就表示一个空闲连接:
客户端如果太长时间没动静,连接器就会自动将它断开,此时想要继续操作就需要重连。这个时间是由参数wait_timeout控制的,通过show variables like 'wait_timeout';可以查看,默认值是28800秒,即8小时。 数据库建立连接的过程通常是比较复杂的,使用中应该尽量减少连接的动作,也就是尽量使用长连接。但全部使用长连接后,有些时候MySql占用的内存会飙涨的很快。这是由于MySql在执行的过程中临时使用的内存是管理在连接对象里面的,这些资源会在连接断开的时候才释放,所以如果长连接累积下来,可能导致内存占用太大,导致被系统强行杀掉OOM(Out Of Memory),进而导致MySql异常重启。
此时有两个解决办法:
查询缓存(Query Cache)主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。
连接建立后,执行查询语句的时候,会先查询缓存,MySQL 会先校验这个 sql 是否执行过,之前执行过的sql语句会以Key-Value 的形式缓存在Mysql内存中,Key 是查询sql语句,Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。
对应的现象就是:同一条语句在MySQL执行两次,第一次和后面的时间是不一样的,后者明显快一些,这就是因为查询缓存的存在。
MySQL 查询不建议使用缓存,有两个原因:
可以通过设置MySQL参数query_cache_type为DEMAND,这样sql默认不使用缓存。
想要在某个sql中使用缓存就在select后使用SQL_CACHE,在某个sql中不想使用缓存就在select后使用SQL_NO_CACHE。例如:
select SQL_NO_CACHE * from XX自MySQL 5.6(2013)以来,默认情况下已禁用查询缓存,而在MySQL 8.0之后,查询缓存的整块功能已被移除,移除的原因:mysqlserverteam/mysql-8-0-retiring-support-for-the-query-cache/,应该还是性能问题。
4 分析器如果存在查询缓存且缓存没有命中,或者没有查询缓存,那么接下来会真正的开始sql执行,首先会走到分析器。分析器主要是用来分析 SQL 语句是来干嘛的,解析sql语句,即词法分析和语法分析:
词法分析和语法分析的具体过程涉及到《编译原理》,本人也不懂……。
5 优化器经过了分析器后,即使sql语句语法没问题,在真正执行sql之前,还需要经过优化器的优化处理。优化器会对你的sql选择他认为最优的执行方案去执行,但优化器的选择也并不一定是最优的。
通常优化器的作用是:
如下Sql:
SELECT C FROM T WHERE A= 'value1' AND B = 'value2';假设A、B字段都使用了单独的索引,其中A上扫描了 100个符合条件的数据行,B 上扫描 50个符合条件的数据行,最终结果只有30个数据行符合两个条件。
如果走A索引会有100个数据行,接着进行匹配找到其中的30个与 B 中的值匹配记录,其中就有 70 次是失败了。先根据 B会有 50 个数据行,接着进行匹配找到其中的 30 个与 A中的值匹配的记录,只有 20次是失败的。显然,选择使用B索引的效率会更高。
5.1 索引选择的逻辑优化器选择索引的目的就是选择一个扫描行数最少的方案。行数越少,磁盘读取越少。但扫描行数不是唯一标准,优化器还会结合是否使用临时表,是否回表,是否排序等因素。
扫描行数怎么判断?真正执行语句之前,mysql不知道具体有多少条,只能根据统计信估算。这个统计信就是索引的“区分度”。索引上不同值越多,区分度越好。而一个索引上不同值的个数称为“基数”,使用show index from tablename可以查看,但在统计信中,基数值都不一定准确。
MySQL是怎样得到索引的基数的呢?实际上是使用采样统计的方法,为什么要采样统计呢?因为把整张表取出来一行行统计,虽然可以得到精确的结果,但是代价太高了,所以只能选择“采样统计”。
采样统计的时候,InnoDB默认会选择N个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。
而数据表是会持续更新的,索引统计信也不会固定不变。所以,当变更的数据行数超过1/M的时候,会自动触发重新做一次索引统计。
在MySQL中,有两种存储索引统计的方式,可以通过设置参数innodb_stats_persistent的值来选择:
由于是采样统计,所以不管N是20还是8,这个基数都是很容易不准的。analyze table tablename命令,可以用来重新统计索引信。force index则可以让查询强制选择指定的索引。
6 执行器经过了优化器之后可以说这个语句具体该如何执行就已经定下来,接下来就进入执行器阶段,开始执行sql。
首先会对当前用户进行执行权限校验,如果没有权限,就会返回没有权限的错误:ERROR 1142 (42000): SELECT command denied to user。
如果有权限,就会去调用引擎的接口,返回接口执行的结果。执行的时候,从第一行开始一行一行的去判断是否满足条件,读取一行的接口都是执行引擎定义好的,直接调用即可,直到取到表的最后一行,有索引的执行起来可能就好点,执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
数据库的慢查询日志中看到看一个rows_examined字段,表示这个语句执行过程中扫描了多少行,这个值就是在执行器每次调用引擎获取数据行的时候累加的,在有些场景下,执行器调用一次,在引擎内部则扫描了很多行,因此引擎扫描行数跟rows_examined并不是完全相同的。
EXPLAIN可以看到执行计划,也可以通过rows字段看到我们扫描了多少行:
7 一条查询sql的执行流程面试中高级工程师的时候,常常被问到:一条sql语句在mysql中如何执行的?那么在我们学习了Mysql的基础架构之后,我们可以总结出一条sql的基本执行流程,实际上就是按照组件顺序一步步的走就行,不需要死记硬背:
上面的流程仅仅是针对一条查询的sql,如果这个sql不是只读sql而是一条更新sql,那么其过程将更加复杂, 将会涉及到Mysql的日志系统、隔离级别、锁等等知识,我们后面再学习。
参考资料:
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!