大家好, 从今天开始我们开启MySQL底层原理的探索里程,今天是第一篇我们将MySQL从一个黑盒状态一点点拨云见日剖析来了解整个MySQL的架构设计和底层组件
现在我们来看看, 对于研发工程师来说数据库是什么东西?平时我们做系统开发时,一般情况下都会连接到一个MySQL数据库上去,去执行各种增删改查语句。如下图所示:
image.png
但是实际上我们在使用MySQL的过程中,总会遇到这样那样的一些问题,比如死锁异常、SQL性能太差、MySQL gone away等等。在遇到MySQL数据库的一些问题时,一般都会上网搜索博客,然后自己尝试捣鼓着解决一下,最后解决了问题,可能也没搞明白里面的原理。
因此我们就要去探索MySQL底层原理的方方面面,以及探索在解决MySQL各种生产实战问题的时候,以及如何基于MySQL底层
原理去进行分析、排查和定位。
大家都知道,如果我们要访问数据库,必须得跟数据库建立一个网络连接,那么这个连接由谁来建立呢?答案就是MySQL驱动,它会在底层跟数据库建立网络连接,有网络连接,接着才能去发送请求给数据库服务器!如下图所示:
image.png
那么我们来思考一个问题,一个golang系统难道只会跟数据库建立一个连接吗?这样肯定是不行的, 对于一个稍微有点流量的站点来说,瞬时会有很多请求打过来,这个时候,都去竞争一个数据库连接的话,性能肯定很低下。
那么如果golang在访问数据库的时候,都创建一个连接,执行完后就释放,这样行不行?首先要明白的是数据库连接是有上限的,因为每次建立一个数据库连接都很耗时(tcp三次握手),好不容易建立好了连接,执行完了SQL语句,你还把数据库连接给销毁了,下一次再重新建立数据库连接,那肯定是效率很低下的 。
image.png
所以一般我们必须要用数据库连接池,也就是说在一个池子里维持多个数据库连接,让多个进程使用里面的不同的数据库连接去
执行SQL语句,然后执行完SQL语句之后,不要销毁这个数据库连接,而是把连接放回池子里,后续还可以继续使用。
基于这样的一个数据库连接池的机制,就可以解决连接争抢和效率问题,如图所示:
image.png
讲到现在我们已经知道,我们任何一个系统都会有一个数据库连接池去访问数据库,也就是说这个系统会有多个数据库连接,供程序并发
的使用。同时我们可能会有多个系统同时去访问一个数据库。
这个时候,我们将目光转移到MySQL本身,对于多系统要与数据库建立很多连接,那么MySQL必然也要维护与系统之间的多个连接才可以,所以,这里我们开始了解MySQL架构体系中的第一个环节,就是连接池。
如下图所示,实际上MySQL中的连接池就是维护了与系统之间的多个数据库连接。除此之外,你的系统每次跟MySQL建立连接的
时候,还会根据你传递过来的账号和密码,进行账号密码的验证,库表权限的验证。
image.png
讲到这里,我们还是把MySQL当作一个黑盒在使用,我们只知道执行了insert语句之后,在表里会多出来一条数据;执行了update语句之后,会对表里的数据进行更改;执行了delete语句之后,会把表里的一条数据删除掉;执行了select语句之后,会从表里查询一些数据出来。如果语句性能有点差?没关系,在表里建几个索引就可以了!
从现在开始就要打破这种把数据库当黑盒子的认知程度,要深入底层,去探索数据库的工作原理以及生产问题的优化手段!
现在我们的数据库服务器的连接池中的某个连接接收到了网络请求,假设就是一条SQL语句,那么我们先思考一个问题,
谁负责从这个连接中去监听网络请求?谁负责从网络连接里把请求数据读取出来?
其实这个时候,一定得有一个线程去处理网络连接,并由它来监听请求以及读取请求数据,比如从网络连接中读取和解析出来一
条我们的系统发送过去的SQL语句, 如下图所示:
image.png
那么接下来着我们思考一下,当MySQL内部的工作线程从一个网络连接中读取出来一个SQL语句之后,此时会如何来执行这个SQL语
句呢?
其实MySQL内部首先提供了一个组件,就是SQL接口(SQL Interface),它是一套执行SQL语句的接口,专门用于执行我们
发送给MySQL的那些增删改查的SQL语句,因此MySQL的工作线程接收到SQL语句之后,就会转交给SQL接口去执行,如下图。
image.png
那么问题来了,SQL接口是如何执行SQL语句呢?你直接把SQL语句交给MySQL,他怎么能看懂和理解这些SQL语句呢?
比如我们来举一个例子,现在我们有这么一个SQL语句:
select id,name,age,sex from `user` where id=1;
我们用人脑是直接就可以处理一下,只要懂SQL语法的人,立马大家就知道他是什么意思,但是MySQL是一个数据库管理系统,他没法直接理解这些SQL语句!
此时有一个关键的组件要出场了,那就是"查询解析器" 这个查询解析器(Parser)就是负责对SQL语句进行解析的,比如对上面那个SQL语句进行一下拆解,拆解 成以下几个部分: 1) 我们现在要从“users”表里查询数据 2) 查询“id”字段的值等于1的那行数据 3) 对查出来的那行数据要提取里面的“id,name,age”三个字段。 所谓的SQL解析,就是按照既定的SQL语法,对我们按照SQL语法规则编写的SQL语句进行解析, 然后理解这个SQL语句要干什么事情
如下图所示:
image.png
当我们通过SQL解析器理解了SQL语句要干什么之后,接着会找查询优化器(Optimizer)来选择一个最优的查询路径。这里我们可以用一个极为通俗简单的例子,来理解一下所谓的最优查询路径是什么。
我们现在理解了一个SQL想要干这么一个事儿:我们现在要从"users"表里查询数据,查询id字段的值等于1的那行数据,对查出来的那行数据要提取里面的"id,name,age"三个字段。
事情明白了,但是到底应该怎么来实现呢?这里我们来简单分析下有以下两种查询路径:
1) 直接定位到"users"表中的id字段等于1的一行数据,然后查出来那行数据的"id,name,age"三个字段 的值就可以了 2) 先把"users"表中的每一行数据的"id,name,age"三个字段的值都查出来,然后从这批数据里过滤出来 id字段等于1的那行数据的"id,name,age"三个字段
其实我们会发现,要完成这个SQL语句的目标,两个路径都可以做到,但
是哪一种更好呢?显然感觉上是第一种查询路径更好一些。
所以查询优化器就是干这个的,它会针对你写的几十行、几百行甚至上千行的复杂SQL语句生成查询路径树,然后从里面选择一条最优的查询路径出来。相当于他会告诉你,你应该按照一个什么样的步骤和顺序,去执行哪些操作,然后一步一步的把SQL语句就给完成了。如下图所示:
image.png
这个时候把查询优化器选择的最优查询路径计划交给底层的存储引擎去真正的执行。这个存储引擎是MySQL的架构设计中很有”特色”的一个环节。
不知道你有没有思考过,真正在执行SQL语句的时候,要么更新数据,要么查询数据,那么数据你觉得存放在哪里?
其实数据库就是一个C、C++语言写出来的系统而已,然后启动之后也是一个进程,执行他里面的各种代码,也就是我们上面所说的那些东西。所以对数据库而言,我们的数据要不然是放在内存里,要不然是放在磁盘文件里,没什么特殊的地方!
所以我们来思考一下,假设我们的数据有的存放在内存里,有的存放在磁盘文件里。那么问题来了, 我们已经知道一个SQL语句要如何执行了,但是我们现在怎么知道哪些数据在内存里?哪些数据在磁盘里? 我们执行的时候是更新内存的数据?还是更新磁盘的数据?我们如果更新磁盘的数据,是先查询哪个磁盘 文件,再更新哪个磁盘文件?到这里是不是感觉一头雾水? 所以这个时候就需要"存储引擎"了,存储引擎其实就是执行SQL语句的,他会按照一定的步骤去查询内存 缓存数据,更新磁盘数据,查询磁盘数据,等等,执行诸如此类的一系列的操作
image.png
在MySQL的架构设计中,SQL接口、SQL解析器、查询优化器其实都是通用的,就是一套组件而已。
但是存储引擎的话,他是支持各种各样的存储引擎的(MySQL支持插件式引擎),比如我们常见的InnoDB、MyISAM、Memory等等,我们是可以选择使用哪种存储引擎来负责具体的SQL语句执行的。
当然现在MySQL5.7都是使用InnoDB存储引擎的,至于存储引擎的原理,后续我们也会深入一步一步分析的
这个时候我们再来思考一个问题,既然存储引擎可以帮助我们去访问内存以及磁盘上的数据,那么是谁来调用存储引擎的接口呢?
我们是不是还漏了一个执行器的概念呢?这个执行器会根据优化器选择的执行方案,去调用存储引擎的接口按照一定的顺序和步骤,就把SQL语句的逻辑给执行了。
这里举个例子,比如执行器可能会先调用存储引擎的一个接口,去获取"users"表中的第一行数据,然后判断一下这个数据的
id字段的值是否等于我们期望的一个值,如果不是的话,那就继续调用存储引擎的接口,去获取"users"表的下一行数据。就是基于上述的思路,执行器就会去根据我们的优化器生成的一套执行计划,然后不停的调用存储引擎的各种接口去完成SQL语句的执行计划,这样整个流程就串起来了,如下图所示:
image.png
到这里,我们将MySQL内部是如何去执行一条SQL的整个流程详细的剖析了一遍,可以简单了解了内部各个组件的作用。下面奉上一张极客时间中MySQL45讲专栏关于MySQL执行的逻辑架构图:
Mysql的逻辑架构图
下一篇我们将会讲"从一条SQL更新语句来了解InnoDB存储引擎的架构设计", 敬请期待。。。。
有疑问加站长微信联系