1、MySQL驱动到底是什么东西?

大家都知道,我们如果要在Java系统中去访问一个MySQL数据库,必须得在系统的依赖中加入一个MySQL驱动,有了这个MySQL驱动才能跟MySQL数据库建立连接,然后执行各种各样的SQL语句。那么这个MySQL驱动到底是个什么东西? 我们先来看下面的一段maven配置,这段maven配置中就引入了一MySQL驱动。这里的mysql-connector-java就是面向Java语言的 MySQL驱动。

  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. <scope>runtime</scope>
  5. <version>5.1.47</version>
  6. </dependency>

大家都知道,如果我们要访问数据库,必须得跟数据库建立一个网络连接,那么这个连接由谁来建立呢? 其实答案就是这个MySQL驱动,他会在底层跟数据库建立网络连接,有网络连接,接着才能去发送请求给数据库服务器!我们看下 图。
image.png

然后当我们跟数据库之间有了网络连接之后,我们的Java代码才能基于这个连接去执行各种各样的增删改查SQL语句, 我们看下图
image.png
所以对于Java语言开发的系统,MySQL会提供Java版本的MySQL驱动,对于PHP、Perl、.NET、Python、Ruby等各种常见的编程语 言,MySQL都会提供对应语言的MySQL驱动,让各种语言编写的系统通过MySQL驱动去访问数据库。

2、数据库连接池到底是用来干什么的?

接着我们来思考一个问题,一个Java系统难道只会跟数据库建立一个连接吗? 这个肯定是不行的,因为我们要明白一个道理,假设我们用Java开发了一个Web系统,是部署在Tomcat中的,那么Tomcat本身肯定 是有多个线程来并发的处理同时接收到的多个请求的,我们看下图。
image.png
这个时候,如果Tomcat中的多个线程并发处理多个请求的时候,都要去抢夺一个连接去访问数据库的话,那效率肯定是很低下的 我们看下面的图
image.png
那么如果Tomcat中的每个线程在每次访问数据库的时候,都基于MySQL驱动去创建一个数据库连接,然后执行SQL语句,然后执行完 之后再销毁这个数据库连接,这样行不行呢? 可能Tomcat中上百个线程会并发的频繁创建数据库连接,执行SQL语句,然后频繁的销毁数据库连接。 上述这个过程反复循环执行,大家觉得可行吗? 这也是非常不好的,因为每次建立一个数据库连接都很耗时,好不容易建立好了连接,执行完了SQL语句,你还把数据库连接给销毁 了,下一次再重新建立数据库连接,那肯定是效率很低下的!如下图。
image.png
所以一般我们必须要使用一个数据库连接池,也就是说在一个池子里维持多个数据库连接,让多个线程使用里面的不同的数据库连接去 执行SQL语句,然后执行完SQL语句之后,不要销毁这个数据库连接,而是把连接放回池子里,后续还可以继续使用。 基于这样的一个数据库连接池的机制,就可以解决多个线程并发的使用多个数据库连接去执行SQL语句的问题,而且还避免了数据库连 接使用完之后就销毁的问题,我们看下图的说明。
image.png

3、MySQL数据库的连接池是用来干什么的?

现在我们已经知道,我们任何一个系统都会有一个数据库连接池去访问数据库,也就是说这个系统会有多个数据库连接,供多线程并发 的使用。同时我们可能会有多个系统同时去访问一个数据库,这都是有可能的。 所以当我们把目光转移到MySQL的时候,我们要来思考一个问题,那就是肯定会有很多系统要与MySQL数据库建立很多个连接,那么 MySQL也必然要维护与系统之间的多个连接,所以MySQL架构体系中的第一个环节,就是连接池。 我们看下面的图,实际上MySQL中的连接池就是维护了与系统之间的多个数据库连接。除此之外,你的系统每次跟MySQL建立连接的 时候,还会根据你传递过来的账号和密码,进行账号密码的验证,库表权限的验证。
image.png

4、Java程序向MYSQL内部发送一条SQL语句,MYSQL内部做了哪些操作

1、 一个不变的原则:网络连接必须让线程来处理

现在假设我们的数据库服务器的连接池中的某个连接接收到了网络请求,假设就是一条SQL语句,那么大家先思考一个问题, 谁负责从这个连接中去监听网络请求?谁负责从网络连接里把请求数据读取出来? 我想很多人恐怕都没思考过这个问题,但是如果大家对计算机基础知识有一个简单了解的话,应该或多或少知道一点,那就是 网络连接必须得分配给一个线程去进行处理,由一个线程来监听请求以及读取请求数据,比如从网络连接中读取和解析出来一 条我们的系统发送过去的SQL语句,如下图所示:
image.png

2、SQL接口:负责处理接收到的SQL语句

接着我们来思考一下,当MySQL内部的工作线程从一个网络连接中读取出来一个SQL语句之后,此时会如何来执行这个SQL语 句呢? 其实SQL是一项伟大的发明,他发明了简单易用的数据读写的语法和模型,哪怕是个产品经理,或者是运营专员,甚至是销售 专员,即使他不会技术,他也能轻松学会使用SQL语句。 但如果你要去执行这个SQL语句,去完成底层数据的增删改查,那这就是一项极度复杂的任务了! 所以MySQL内部首先提供了一个组件,就是SQL接口(SQL Interface),他是一套执行SQL语句的接口,专门用于执行我们 发送给MySQL的那些增删改查的SQL语句 因此MySQL的工作线程接收到SQL语句之后,就会转交给SQL接口去执行,如下图。
image.png

3、查询解析器:让MySQL能看懂SQL语句

接着下一个问题来了,SQL接口怎么执行SQL语句呢?你直接把SQL语句交给MySQL,他能看懂和理解这些SQL语句吗? 比如我们来举一个例子,现在我们有这么一个SQL语句: select id,name,age from users where id=1 这个SQL语句,我们用人脑是直接就可以处理一下,只要懂SQL语法的人,立马大家就知道他是什么意思,但是MySQL自己本 身也是一个系统,是一个数据库管理系统,他没法直接理解这些SQL语句! 所以此时有一个关键的组件要出场了,那就是查询解析器 这个查询解析器(Parser)就是负责对SQL语句进行解析的,比如对上面那个SQL语句进行一下拆解,拆解成以下几个部分: 我们现在要从“users”表里查询数据 查询“id”字段的值等于1的那行数据 对查出来的那行数据要提取里面的“id,name,age”三个字段。 所谓的SQL解析,就是按照既定的SQL语法,对我们按照SQL语法规则编写的SQL语句进行解析,然后理解这个SQL语句要干 什么事情,如下图所示:
image.png

4、查询优化器:选择最优的查询路径

当我们通过解析器理解了SQL语句要干什么之后,接着会找查询优化器(Optimizer)来选择一个最优的查询路径。 可能有同学这里就不太理解什么是最优的查询路径了,这个看起来确实很抽象,当然,这个查询优化器的工作原理,后续将会 是我们分析的重点,大家现在不用去纠结他的原理。 但是我们可以用一个极为通俗简单的例子,让大家理解一下所谓的最优查询路径是什么。 就用我们刚才讲的那个例子好了,我们现在理解了一个SQL想要干这么一个事儿:我们现在要从“users”表里查询数据,查 询“id”字段的值等于1的那行数据,对查出来的那行数据要提取里面的“id,name,age”三个字段。 事是明白了,但是到底应该怎么来实现呢? 你看,要完成这个事儿我们有以下几个查询路径(纯属用于大家理解的例子,不代表真实的MySQL原理,但是通过这个例 子,大家肯定能理解所谓最优查询路径的意思): 直接定位到“users”表中的“id”字段等于1的一行数据,然后查出来那行数据的“id,name,age”三个字段的值就可以了 先把“users”表中的每一行数据的“id,name,age”三个字段的值都查出来,然后从这批数据里过滤出来“id”字段等于1的 那行数据的“id,name,age”三个字段 上面这就是一个最简单的SQL语句的两种实现路径,其实我们会发现,要完成这个SQL语句的目标,两个路径都可以做到,但 是哪一种更好呢?显然感觉上是第一种查询路径更好一些。 所以查询优化器大概就是干这个的,他会针对你编写的几十行、几百行甚至上千行的复杂SQL语句生成查询路径树,然后从里 面选择一条最优的查询路径出来。 相当于他会告诉你,你应该按照一个什么样的步骤和顺序,去执行哪些操作,然后一步一步的把SQL语句就给完成了。
image.png

5、调用存储引擎接口,真正执行SQL语句

最后一步,就是把查询优化器选择的最优查询路径,也就是你到底应该按照一个什么样的顺序和步骤去执行这个SQL语句的计 划,把这个计划交给底层的存储引擎去真正的执行。这个存储引擎是MySQL的架构设计中很有特色的一个环节。 不知道大家是否思考过,真正在执行SQL语句的时候,要不然是更新数据,要不然是查询数据,那么数据你觉得存放在哪里? 说白了,数据库也不是什么神秘莫测的东西,你可以把他理解为本身就是一个类似你平时写的图书馆管理系统、电信计费系 统、电商订单系统之类的系统罢了。 数据库自己就是一个编程语言写出来的系统而已,然后启动之后也是一个进程,执行他里面的各种代码,也就是我们上面所说 的那些东西。所以对数据库而言,我们的数据要不然是放在内存里,要不然是放在磁盘文件里,没什么特殊的地方! 所以我们来思考一下,假设我们的数据有的存放在内存里,有的存放在磁盘文件里,如下图所示。
image.png
那么现在问题来了,我们已经知道一个SQL语句要如何执行了,但是我们现在怎么知道哪些数据在内存里?哪些数据在磁盘 里?我们执行的时候是更新内存的数据?还是更新磁盘的数据?我们如果更新磁盘的数据,是先查询哪个磁盘文件,再更新哪 个磁盘文件? 是不是感觉一头雾水 所以这个时候就需要存储引擎了,存储引擎其实就是执行SQL语句的,他会按照一定的步骤去查询内存缓存数据,更新磁盘数 据,查询磁盘数据,等等,执行诸如此类的一系列的操作,如下图所示。
image.png

MySQL的架构设计中,SQL接口、SQL解析器、查询优化器其实都是通用的,他就是一套组件而已。 但是存储引擎的话,他是支持各种各样的存储引擎的,比如我们常见的InnoDB、MyISAM、Memory等等,我们是可以选择 使用哪种存储引擎来负责具体的SQL语句执行的。 当然现在MySQL一般都是使用InnoDB存储引擎的

6、执行器:根据执行计划调用存储引擎的接口

那么看完存储引擎之后,我们回过头来思考一个问题,存储引擎可以帮助我们去访问内存以及磁盘上的数据,那么是谁来调用 存储引擎的接口呢? 其实我们现在还漏了一个执行器的概念,这个执行器会根据优化器选择的执行方案,去调用存储引擎的接口按照一定的顺序和 步骤,就把SQL语句的逻辑给执行了。 举个例子,比如执行器可能会先调用存储引擎的一个接口,去获取“users”表中的第一行数据,然后判断一下这个数据的 “id”字段的值是否等于我们期望的一个值,如果不是的话,那就继续调用存储引擎的接口,去获取“users”表的下一行数 据。 就是基于上述的思路,执行器就会去根据我们的优化器生成的一套执行计划,然后不停的调用存储引擎的各种接口去完成SQL 语句的执行计划,大致就是不停的更新或者提取一些数据出来 我们看下图的示意
image.png

5、 更新语句在MySQL中是如何执行的?

之前我们已经分析了MySQL架构上的整体设计原理,现在对一条SQL语句从我们的系统层面发送到MySQL中,然后一 步一步执行这条SQL的流程,都有了一个整体的了解。 我们已经知道了,MySQL最常用的就是InnoDB存储引擎,那么我们今天借助一条更新语句的执行,来初步的了解一 下InnoDB存储引擎的架构设计。 首先假设我们有一条SQL语句是这样的: update users set name=’xxx’ where id=10 那么我们先想一下这条SQL语句是如何执行的? 首先肯定是我们的系统通过一个数据库连接发送到了MySQL上,然后肯定会经过SQL接口、解析器、优化器、执行器 几个环节,解析SQL语句,生成执行计划,接着去由执行器负责这个计划的执行,调用InnoDB存储引擎的接口去执 行。 所以先看下图,大致还是会走下图的这个流程
image.png
今天我们就来探索一下这个存储引擎里的架构设计,以及如何基于存储引擎完成一条更新语句的执行

1、InnoDB的重要内存结构:缓冲池

InnoDB存储引擎中有一个非常重要的放在内存里的组件,就是缓冲池(Buffer Pool),这里面会缓存很多的数据, 以便于以后在查询的时候,万一你要是内存缓冲池里有数据,就可以不用去查磁盘了,我们看下图。
image.png
所以当我们的InnoDB存储 引擎要执行更新语句的时候 ,比如对“id=10”这一行数据,他其实会先将“id=10”这一行数据看看是否在缓冲池里,如果不在的 话,那么会直接从磁盘里加载到缓冲池里来,而且接着还会对这行记录加独占锁。 因为我们想一下,在我们更新“id=10”这一行数据的时候,肯定是不允许别人同时更新的,所以必须要对这行记录加 独占锁

2、undo日志文件:如何让你更新的数据可以回滚?

接着下一步,假设“id=10”这行数据的name原来是“zhangsan”,现在我们要更新为“xxx”,那么此时我们得先 把要更新的原来的值“zhangsan”和“id=10”这些信息,写入到undo日志文件中去。 其实稍微对数据库 有一点了解的同学都应该知道,如果我们执行一个更新语句,要是他是在一个事务里的话,那么事 务提交之前我们都是可以对数据进行回滚的,也就是把你更新为“xxx”的值回滚到之前的“zhangsan”去。 所以为了考虑到未来可能要回滚数据的需要,这里会把你更新前的值写入undo日志文件,我们看下图。
image.png

3、更新buffer pool中的缓存数据

当我们把要更新的那行记录从磁盘文件加载到缓冲池,同时对他加锁之后,而且还把更新前的旧值写入undo日志文件 之后,我们就可以正式开始更新这行记录了,更新的时候,先是会更新缓冲池中的记录,此时这个数据就是脏数据 了。 这里所谓的更新内存缓冲池里的数据,意思就是把内存里的“id=10”这行数据的name字段修改为“xxx” 那么为什么说此时这行数据就是脏数据了呢? 因为这个时候磁盘上“id=10”这行数据的name字段还是“zhangsan”,但是内存里这行数据已经被修改了,所以 就会叫他是脏数据。 我们看下图,我同时把几个步骤的序号标记出来了。
image.png

4、Redo Log Buffer:万一系统宕机,如何避免数据丢失?

接着我们来思考一个问题,按照上图的说明,现在已经把内存里的数据进行了修改,但是磁盘上的数据还没修改 那么此时万一MySQL所在的机器宕机了,必然会导致内存里修改过的数据丢失,这可怎么办呢? 这个时候,就必须要把对内存所做的修改写入到一个Redo Log Buffer里去,这也是内存里的一个缓冲区,是用来存 放redo日志的 所谓的redo日志,就是记录下来你对数据做了什么修改,比如对“id=10这行记录修改了name字段的值为xxx”,这 就是一个日志。 我们先看下图的示意
image.png
这个redo日志其实是用来在MySQL突然宕机的时候,用来恢复你更新过的数据的,但是我们现在还没法直接讲解redo 是如何使用的,毕竟现在redo日志还仅仅停留在内存缓冲里

5、 如果还没提交事务,MySQL宕机了怎么办?

所以我们都知道,其实在数据库中,哪怕执行一条SQL语句,其实也可以是一个独立的事务,只有当你提交事务之 后,SQL语句才算执行结束。 所以这里我们都知道,到目前为止,其实还没有提交事务,那么此时如果MySQL崩溃,必然导致内存里Buffer Pool中 的修改过的数据都丢失,同时你写入Redo Log Buffer中的redo日志也会丢失 我们看下图
image.png
那么此时数据丢失要紧吗? 其实是不要紧的,因为你一条更新语句,没提交事务,就代表他没执行成功,此时MySQL宕机虽然导致内存里的数据 都丢失了,但是你会发现,磁盘上的数据依然还停留在原样子。 也就是说,“id=1”的那行数据的name字段的值还是老的值,“zhangsan”,所以此时你的这个事务就是执行失败 了,没能成功完成更新,你会收到一个数据库的异常。然后当mysql重启之后,你会发现你的数据并没有任何变化。 所以此时如果mysql宕机,不会有任何的问题。

6、提交事务的时候将redo日志写入磁盘中

接着我们想要提交一个事务了,此时就会根据一定的策略把redo日志从redo log buffer里刷入到磁盘文件里去。 此时这个策略是通过innodb_flush_log_at_trx_commit来配置的,他有几个选项。

1、innodb_flush_log_at_trx_commit=0

那么你提交事务的时候,不会把redo log buffer里的数据刷入磁盘文件的,此时可能你都 提交事务了,结果mysql宕机了,然后此时内存里的数据全部丢失。 相当于你提交事务成功了,但是由于MySQL突然宕机,导致内存中的数据和redo日志都丢失了,我们看下图:
image.png

1、innodb_flush_log_at_trx_commit=1

你提交事务的时候,就必须把redo log从内存刷入到磁盘文件里去,只要事务提交成功,那么redo log就 必然在磁盘里了,我们看下图:
image.png
那么只要提交事务成功之后,redo日志一定在磁盘文件里,此时你肯定会有一条redo日志说了,“我此时对哪个数据做了一个什么修 改,比如name字段修改为xxx了”。 然后哪怕此时buffer pool中更新过的数据还没刷新到磁盘里去,此时内存里的数据是已经更新过的“name=xxx”,然后磁盘上的数 据还是没更新过的“name=zhangsan”。 我们看下图,提交事务之后,可能处于的一个状态。
image.png
此时如果说提交事务后处于上图的状态,然后mysql系统突然崩溃了,此时会如何?会丢失数据吗? 肯定不会啊,因为虽然内存里的修改成name=xxx的数据会丢失,但是redo日志里已经说了,对某某数据做了修改 name=xxx。 所以此时mysql重启之后,他可以根据redo日志去恢复之前做过的修改,我们看下图。
image.png

3、innodb_flush_log_at_trx_commit=2

他的意思就是,提交事务的时候,把redo日志写入磁盘文件对应的os cache缓存里去,而不是直接进入磁盘文件,可 能1秒后才会把os cache里的数据写入到磁盘文件里去。 这种模式下,你提交事务之后,redo log可能仅仅停留在os cache内存缓存里,没实际进入磁盘文件,万一此时你要 是机器宕机了,那么os cache里的redo log就会丢失,同样会让你感觉提交事务了,结果数据丢了,看下图。
image.png

6、 聊聊binlog是什么?

1、 MySQL binlog到底是什么东西?

接着我们来看看MySQL binlog到底是个什么东西? 实际上我们之前说的redo log,他是一种偏向物理性质的重做日志,因为他里面记录的是类似这样的东西,“对哪个 数据页中的什么记录,做了个什么修改”。 而且redo log本身是属于InnoDB存储引擎特有的一个东西。 而binlog叫做归档日志,他里面记录的是偏向于逻辑性的日志,类似于“对users表中的id=10的一行数据做了更新操 作,更新以后的值是什么” binlog不是InnoDB存储引擎特有的日志文件,是属于mysql server自己的日志文件。

2、 提交事务的时候,同时会写入binlog

所以其实我们上一讲讲到,在我们提交事务的时候,会把redo log日志写入磁盘文件中去。然后其实在提交事务的时 候,我们同时还会把这次更新对应的binlog日志写入到磁盘文件中去,如下图所示。
image.png

3、binlog日志的刷盘策略分析

1、sync_binlog=0

对于binlog日志,其实也有不同的刷盘策略,有一个sync_binlog参数可以控制binlog的刷盘策略,他的默认值是0, 此时你把binlog写入磁盘的时候,其实不是直接进入磁盘文件,而是进入os cache内存缓存。 所以跟之前分析的一样,如果此时机器宕机,那么你在os cache里的binlog日志是会丢失的,我们看下图的示意
image.png

1、 sync_binlog =1

如果要是把sync_binlog参数设置为1的话,那么此时会强制在提交事务的时候,把binlog直接写入到磁盘文件里去, 那么这样提交事务之后,哪怕机器宕机,磁盘上的binlog是不会丢失的,如下图所示
image.png

4、基于binlog和redo log完成事务的提交

当我们把binlog写入磁盘文件之后,接着就会完成最终的事务提交,此时会把本次更新对应的binlog文件名称和这次 更新的binlog日志在文件里的位置,都写入到redo log日志文件里去,同时在redo log日志文件里写入一个commit标 记。 在完成这个事情之后,才算最终完成了事务的提交,我们看下图的示意。
image.png

5、最后一步在redo日志中写入commit标记的意义是什么?

这时候肯定有同学会问了,最后在redo日志中写入commit标记有什么意义呢? 说白了,他其实是用来保持redo log日志与binlog日志一致的。 我们来举个例子,假设我们在提交事务的时候,一共有上图中的5、6、7三个步骤,必须是三个步骤都执行完毕,才算 是提交了事务。那么在我们刚完成步骤5的时候,也就是redo log刚刷入磁盘文件的时候,mysql宕机了,此时怎么 办? 这个时候因为没有最终的事务commit标记在redo日志里,所以此次事务可以判定为不成功。不会说redo日志文件里 有这次更新的日志,但是binlog日志文件里没有这次更新的日志,不会出现数据不一致的问题。 如果要是完成步骤6的时候,也就是binlog写入磁盘了,此时mysql宕机了,怎么办? 同理,因为没有redo log中的最终commit标记,因此此时事务提交也是失败的。 必须是在redo log中写入最终的事务commit标记了,然后此时事务提交成功,而且redo log里有本次更新对应的日 志,binlog里也有本次更新对应的日志 ,redo log和binlog完全是一致的。

6、 后台IO线程随机将内存更新后的脏数据刷回磁盘

现在我们假设已经提交事务了,此时一次更新“update users set name=’xxx’ where id=10”,他已经把内存里的 buffer pool中的缓存数据更新了,同时磁盘里有redo日志和binlog日志,都记录了把我们指定的“id=10”这行数据 修改了“name=’xxx’”。 此时我们会思考一个问题了,但是这个时候磁盘上的数据文件里的“id=10”这行数据的name字段还是等于 zhangsan这个旧的值啊! 所以MySQL有一个后台的IO线程,会在之后某个时间里,随机的把内存buffer pool中的修改后的脏数据给刷回到磁 盘上的数据文件里去,我们看下图:
image.png
当上图中的IO线程把buffer pool里的修改后的脏数据刷回磁盘的之后,磁盘上的数据才会跟内存里一样,都是 name=xxx这个修改以后的值了! 在你IO线程把脏数据刷回磁盘之前,哪怕mysql宕机崩溃也没关系,因为重启之后,会根据redo日志恢复之前提交事 务做过的修改到内存里去,就是id=10的数据的name修改为了xxx,然后等适当时机,IO线程自然还是会把这个修改 后的数据刷到磁盘上的数据文件里去的

7 、基于更新数据的流程,总结一下InnoDB存储引擎的架构原理

大家通过一次更新数据的流程,就可以清晰地看到,InnoDB存储引擎主要就是包含了一些buffer pool、redo log buffer等内存里的缓存数据,同时还包含了一些undo日志文件,redo日志文件等东西,同时mysql server自己还有 binlog日志文件。 在你执行更新的时候,每条SQL语句,都会对应修改buffer pool里的缓存数据、写undo日志、写redo log buffer几 个步骤; 但是当你提交事务的时候,一定会把redo log刷入磁盘,binlog刷入磁盘,完成redo log中的事务commit标记;最后 后台的IO线程会随机的把buffer pool里的脏数据刷入磁盘里去。