关于mysql:详解一条-SQL-的执行过程

8次阅读

共计 7509 个字符,预计需要花费 19 分钟才能阅读完成。

前言

天天和数据库打交道,一天能写上几十条 SQL 语句,但你晓得咱们的零碎是如何和数据库交互的吗?MySQL 如何帮咱们存储数据、又是如何帮咱们治理事务?…. 是不是感觉真的除了写几个「select * from dual」外根本脑子一片空白?这篇文章就将带你走进 MySQL 的世界,让你彻底理解零碎到底是如何和 MySQL 交互的,MySQL 在承受到咱们发送的 SQL 语句时又别离做了哪些事件。

MySQL 驱动

咱们的零碎在和 MySQL 数据库进行通信的时候,总不可能是平白无故的就能接管和发送申请,就算是你没有做什么操作,那总该是有其余的“人”帮咱们做了一些事件,基本上应用过 MySQL 数据库的程序员多多少少都会晓得 MySQL 驱动这个概念的。就是这个 MySQL 驱动在底层帮咱们做了对数据库的连贯,只有建设了连贯了,才可能有前面的交互。
这样的话,在零碎和 MySQL 进行交互之前,MySQL 驱动会帮咱们建设好连贯,而后咱们只须要发送 SQL 语句就能够执行 CRUD 了。一次 SQL 申请就会建设一个连贯,多个申请就会建设多个连贯,那么问题来了,咱们零碎必定不是一个人在应用的,换句话说必定是存在多个申请同时去争抢连贯的状况。咱们的 web 零碎个别都是部署在 tomcat 容器中的,而 tomcat 是能够并发解决多个申请的,这就会导致多个申请会去建设多个连贯,而后应用完再都去敞开,这样会有什么问题呢?ava 零碎在通过 MySQL 驱动和 MySQL 数据库连贯的时候是基于 TCP/IP 协定的,所以如果每个申请都是新建连贯和销毁连贯,那这样势必会造成不必要的节约和性能的降落,也就说下面的多线程申请的时候频繁的创立和销毁连贯显然是不合理的。必然会大大降低咱们零碎的性能,然而如果给你提供一些固定的用来连贯的线程,这样是不是不须要重复的创立和销毁连贯了呢?置信懂行的敌人会会心一笑,没错,说的就是数据库连接池。

数据库连接池:保护肯定的连接数,不便零碎获取连贯,应用就去池子中获取,用完放回去就能够了,咱们不须要关怀连贯的创立与销毁,也不须要关怀线程池是怎么去保护这些连贯的。

常见的数据库连接池有 Druid、C3P0、DBCP,连接池实现原理在这里就不深刻探讨了,采纳连接池大大节俭了一直创立与销毁线程的开销,这就是有名的「池化」思维,不论是线程池还是 HTTP 连接池,都能看到它的身影。

数据库连接池

到这里,咱们曾经晓得的是咱们的零碎在拜访 MySQL 数据库的时候,建设的连贯并不是每次申请都会去创立的,而是从数据库连接池中去获取,这样就解决了因为重复的创立和销毁连贯而带来的性能损耗问题了。不过这里有个小问题,业务零碎是并发的,而 MySQL 承受申请的线程呢,只有一个?

其实 MySQL 的架构体系中也曾经提供了这样的一个池子,也是数据库连池。单方都是通过数据库连接池来治理各个连贯的,这样一方面线程之前不须要是争抢连贯,更重要的是不须要重复的创立的销毁连贯。

至此零碎和 MySQL 数据库之间的连贯问题曾经阐明分明了。那么 MySQL 数据库中的这些连贯是怎么来解决的,又是谁来解决呢?

网络连接必须由线程来解决

对计算根底略微有一点理解的的同学都是晓得的,网络中的连贯都是由线程来解决的,所谓网络连接说白了就是一次申请,每次申请都会有相应的线程去解决的。也就是说对于 SQL 语句的申请在 MySQL 中是由一个个的线程去解决的。

那这些线程会怎么去解决这些申请?会做哪些事件?

SQL 接口

MySQL 中解决申请的线程在获取到申请当前获取 SQL 语句去交给 SQL 接口去解决。

查问解析器

如果当初有这样的一个 SQL

SELECT stuName,age,sex FROM students WHERE id=1

然而这个 SQL 是写给咱们人看的,机器哪里晓得你在说什么?这个时候解析器就上场了。他会将 SQL 接口传递过去的 SQL 语句进行解析,翻译成 MySQL 本人能意识的语言,至于怎么解析的就不须要再深究了,无非是本人一套相干的规定。

当初 SQL 曾经被解析成 MySQL 意识的样子的,那下一步是不是就是执行吗?实践上是这样子的,然而 MySQL 的弱小远不止于此,他还会帮咱们抉择最优的查问门路。

什么叫最优查问门路?就是 MySQL 会依照本人认为的效率最高的形式去执行查问

具体是怎么做到的呢?这就要说到 MySQL 的查问优化器了

MySQL 查问优化器

查问优化器外部具体怎么实现的咱们不须要是关怀,我须要晓得的是 MySQL 会帮我去应用他本人认为的最好的形式去优化这条 SQL 语句,并生成一条条的执行打算,比方你创立了多个索引,MySQL 会根据老本最小准则来抉择应用对应的索引,这里的老本次要包含两个方面, IO 老本和 CPU 老本

IO 老本: 即从磁盘把数据加载到内存的老本,默认状况下,读取数据页的 IO 老本是 1,MySQL 是以页的模式读取数据的,即当用到某个数据时,并不会只读取这个数据,而会把这个数据相邻的数据也一起读到内存中,这就是有名的程序局部性原理,所以 MySQL 每次会读取一整页,一页的老本就是 1。所以 IO 的老本次要和页的大小无关

CPU 老本:将数据读入内存后,还要检测数据是否满足条件和排序等 CPU 操作的老本,显然它与行数无关,默认状况下,检测记录的老本是 0.2。

MySQL 优化器 会计算「IO 老本 + CPU」老本最小的那个索引来执行

优化器执行选出最优索引等步骤后,会去调用存储引擎接口,开始去执行被 MySQL 解析过和优化过的 SQL 语句

存储引擎

查问优化器会调用存储引擎的接口,去执行 SQL,也就是说真正执行 SQL 的动作是在存储引擎中实现的。数据是被寄存在内存或者是磁盘中的(存储引擎是一个十分重要的组件,前面会具体介绍)

执行器

执行器是一个十分重要的组件,因为后面那些组件的操作最终必须通过执行器去调用存储引擎接口能力被执行。执行器最终最依据一系列的执行打算去调用存储引擎的接口去实现 SQL 的执行

初识存储引擎

咱们以一个更新的 SQL 语句来阐明,SQL 如下

UPDATE students SET stuName = ‘ 小强 ’ WHERE id = 1

当咱们零碎收回这样的查问去交给 MySQL 的时候,MySQL 会依照咱们下面介绍的一系列的流程最终通过执行器调用存储引擎去执行,流程图就是下面那个。在执行这个 SQL 的时候 SQL 语句对应的数据要么是在内存中,要么是在磁盘中,如果间接在磁盘中操作,那这样的随机 IO 读写的速度必定让人无奈承受的,所以每次在执行 SQL 的时候都会将其数据加载到内存中,这块内存就是 InnoDB 中一个十分重要的组件:缓冲池 Buffer Pool

Buffer Pool

Buffer Pool(缓冲池)是 InnoDB 存储引擎中十分重要的内存构造,顾名思义,缓冲池其实就是相似 Redis 一样的作用,起到一个缓存的作用,因为咱们都晓得 MySQL 的数据最终是存储在磁盘中的,如果没有这个 Buffer Pool 那么咱们每次的数据库申请都会磁盘中查找,这样必然会存在 IO 操作,这必定是无奈承受的。然而有了 Buffer Pool 就是咱们第一次在查问的时候会将查问的后果存到 Buffer Pool 中,这样前面再有申请的时候就会先从缓冲池中去查问,如果没有再去磁盘中查找,而后再放到 Buffer Pool 中

这条 SQL 语句的执行步骤大抵是这样子的

1.innodb 存储引擎会在缓冲池中查找 id=1 的这条数据是否存在
2. 发现不存在,那么就会去磁盘中加载,并将其寄存在缓冲池中
3. 该条记录会被加上一个独占锁

undo 日志文件:记录数据被批改前的样子

undo 顾名思义,就是没有做,没产生的意思。undo log 就是没有产生事件(本来事件是什么)的一些日志

咱们刚刚曾经说了,在筹备更新一条语句的时候,该条语句曾经被加载到 Buffer pool 中了,实际上这里还有这样的操作,就是在将该条语句加载到 Buffer Pool 中的时候同时会往 undo 日志文件中插入一条日志,也就是将 id=1 的这条记录的原来的值记录下来。

这样做的目标是什么?

Innodb 存储引擎的最大特点就是反对事务,如果本次更新失败,也就是事务提交失败,那么该事务中的所有的操作都必须回滚到执行前的样子,也就是说当事务失败的时候,也不会对原始数据有影响
这里说句额定话,其实 MySQL 也是一个零碎,就好比咱们平时开发的 java 的性能零碎一样,MySQL 应用的是本人相应的语言开发进去的一套零碎而已,它依据本人须要的性能去设计对应的性能,它即然能做到哪些事件,那么必然是设计者们当初这么定义或者是依据理论的场景变更演变而来的。所以大家放平心态,把 MySQL 当作一个零碎去理解相熟他。

到这一步,咱们的执行的 SQL 语句曾经被加载到 Buffer Pool 中了,而后开始更新这条语句,更新的操作理论是在 Buffer Pool 中执行的,那问题来了,依照咱们平时开发的一套实践缓冲池中的数据和数据库中的数据不统一时候,咱们就认为缓存中的数据是脏数据,那此时 Buffer Pool 中的数据岂不是成了脏数据?没错,目前这条数据就是脏数据,Buffer Pool 中的记录是小强 数据库中的记录是旺财,这种状况 MySQL 是怎么解决的呢,持续往下看

redo 日志文件:记录数据被批改后的样子

除了从磁盘中加载文件和将操作前的记录保留到 undo 日志文件中,其余的操作是在内存中实现的,内存中的数据的特点就是:断电失落。如果此时 MySQL 所在的服务器宕机了,那么 Buffer Pool 中的数据会全副失落的。这个时候 redo 日志文件就须要来大显身手了

画外音:redo 日志文件是 InnoDB 特有的,他是存储引擎级别的,不是 MySQL 级别的

redo 记录的是数据批改之后的值,不论事务是否提交都会记录下来,例如,此时将要做的是 update students set stuName=’ 小强 ’ where id=1; 那么这条操作就会被记录到 redo log buffer 中,啥?怎么又进去一个 redo log buffer , 很简略,MySQL 为了提高效率,所以将这些操作都先放在内存中去实现,而后会在某个机会将其长久化到磁盘中。

截至目前,咱们应该都相熟了 MySQL 的执行器调用存储引擎是怎么将一条 SQL 加载到缓冲池和记录哪些日志的,流程如下:
1. 筹备更新一条 SQL 语句
2.MySQL(innodb)会先去缓冲池(BufferPool)中去查找这条数据,没找到就会去磁盘中查找,如果查找到就会将这条数据加载到缓冲池(BufferPool)中
3. 在加载到 Buffer Pool 的同时,会将这条数据的原始记录保留到 undo 日志文件中
4.innodb 会在 Buffer Pool 中执行更新操作
5. 更新后的数据会记录在 redo log buffer 中
下面说的步骤都是在失常状况下的操作,然而程序的设计和优化并不仅是为了这些失常状况而去做的,也是为了那些临界区和极其状况下呈现的问题去优化设计的
这个时候如果服务器宕机了,那么缓存中的数据还是失落了。真烦,居然数据总是失落,那能不能不要放在内存中,间接保留到磁盘呢?很显然不行,因为在下面也曾经介绍了,在内存中的操作目标是为了提高效率。

此时,如果 MySQL 真的宕机了,那么没关系的,因为 MySQL 会认为本次事务是失败的,所以数据仍旧是更新前的样子,并不会有任何的影响。

好了,语句也更新好了那么须要将更新的值提交啊,也就是须要提交本次的事务了,因为只有事务胜利提交了,才会将最初的变更保留到数据库,在提交事务前依然会具备相干的其余操作

将 redo Log Buffer 中的数据长久化到磁盘中,就是将 redo log buffer 中的数据写入到 redo log 磁盘文件中,个别状况下,redo log Buffer 数据写入磁盘的策略是立刻刷入磁盘(具体策略状况在上面小总结出会具体介绍)
如果 redo log Buffer 刷入磁盘后,数据库服务器宕机了,那咱们更新的数据怎么办?此时数据是在内存中,数据岂不是失落了?不,这次数据就不会失落了,因为 redo log buffer 中的数据曾经被写入到磁盘了,曾经被长久化了,就算数据库宕机了,在下次重启的时候 MySQL 也会将 redo 日志文件内容复原到 Buffer Pool 中(这边我的了解是和 Redis 的长久化机制是差不多的,在 Redis 启动的时候会查看 rdb 或者是 aof 或者是两者都查看,依据长久化的文件来将数据恢复到内存中)

到此为止,从执行器开始调用存储引擎接口做了哪些事件呢?
1. 筹备更新一条 SQL 语句

2.MySQL(innodb)会先去缓冲池(BufferPool)中去查找这条数据,没找到就会去磁盘中查找,如果查找到就会将这条数据加载

到缓冲池(BufferPool)中 3. 在加载到 Buffer Pool 的同时,会将这条数据的原始记录保留到 undo 日志文件中

4.innodb 会在 Buffer Pool 中执行更新操作

5. 更新后的数据会记录在 redo log buffer 中

— 到此是后面曾经总结过的 —

6.MySQL 提交事务的时候,会将 redo log buffer 中的数据写入到 redo 日志文件中 刷磁盘能够通过 innodb_flush_log_at_trx_commit 参数来设置

值为 0 示意不刷入磁盘

值为 1 示意立刻刷入磁盘

值为 2 示意先刷到 os cache

7.myslq 重启的时候会将 redo 日志复原到缓冲池中

截止到目前为止,MySQL 的执行器调用存储引擎的接口去执行【执行打算】提供的 SQL 的时候 InnoDB 做了哪些事件也就根本差不多了,然而这还没完。上面还须要介绍下 MySQL 级别的日志文件 bin log
bin log 日志文件:记录整个操作过程
下面介绍到的 redo log 是 InnoDB 存储引擎特有的日志文件,而 bin log 属于是 MySQL 级别的日志。redo log 记录的货色是偏差于物理性质的,如:“对什么数据,做了什么批改”。bin log 是偏差于逻辑性质的,相似于:“对 students 表中的 id 为 1 的记录做了更新操作”两者的次要特点总结如下:

bin log 文件是如何刷入磁盘的?

bin log 的刷盘是有相干的策略的,策略能够通过 sync_bin log 来批改,默认为 0,示意先写入 os cache,也就是说在提交事务的时候,数据不会间接到磁盘中,这样如果宕机 bin log 数据依然会失落。所以倡议将 sync_bin log 设置为 1 示意间接将数据写入到磁盘文件中。

刷入 bin log 有以下几种模式

1、STATMENT

基于 SQL 语句的复制(statement-based replication, SBR),每一条会批改数据的 SQL 语句会记录到 bin log 中

【长处】:不须要记录每一行的变动,缩小了 bin log 日志量,节约了 IO , 从而进步了性能

【毛病】:在某些状况下会导致主从数据不统一,比方执行 sysdate()、slepp()等

2、ROW

基于行的复制(row-based replication, RBR),不记录每条 SQL 语句的上下文信息,仅需记录哪条数据被批改了

【长处】:不会呈现某些特定状况下的存储过程、或 function、或 trigger 的调用和触发无奈被正确复制的问题

【毛病】:会产生大量的日志,尤其是 alter table 的时候会让日志暴涨

3、MIXED

基于 STATMENT 和 ROW 两种模式的混合复制(mixed-based replication, MBR),个别的复制应用 STATEMENT 模式保留 bin log,对于 STATEMENT 模式无奈复制的操作应用 ROW 模式保留 bin log

那既然 bin log 也是日志文件,那它是在什么记录数据的呢?

其实 MySQL 在提交事务的时候,不仅仅会将 redo log buffer 中的数据写入到 redo log 文件中,同时也会将本次批改的数据记录到 bin log 文件中,同时会将本次批改的 bin log 文件名和批改的内容在 bin log 中的地位记录到 redo log 中,最初还会在 redo log 最初写入 commit 标记,这样就示意本次事务被胜利的提交了。

如果在数据被写入到 bin log 文件的时候,刚写完,数据库宕机了,数据会失落吗?

首先能够确定的是,只有 redo log 最初没有 commit 标记,阐明本次的事务肯定是失败的。然而数据是没有失落了,因为曾经被记录到 redo log 的磁盘文件中了。在 MySQL 重启的时候,就会将 redo log 中的数据恢复(加载)到 Buffer Pool 中。

好了,到目前为止,一个更新操作咱们根本介绍得差不多,然而你有没有感觉少了哪件事件还没有做?是不是你也发现这个时候被更新记录仅仅是在内存中执行的,哪怕是宕机又复原了也仅仅是将更新后的记录加载到 Buffer Pool 中,这个时候 MySQL 数据库中的这条记录仍旧是旧值,也就是说内存中的数据在咱们看来仍旧是脏数据,那这个时候怎么办呢?

其实 MySQL 会有一个后盾线程,它会在某个机会将咱们 Buffer Pool 中的脏数据刷到 MySQL 数据库中,这样就将内存和数据库的数据放弃对立了。

本文总结

到此,对于 Buffer Pool、Redo Log Buffer 和 undo log、redo log、bin log 概念以及关系就根本差不多了。

咱们再回顾下
1.Buffer Pool 是 MySQL 的一个十分重要的组件,因为针对数据库的增删改操作都是在 Buffer Pool 中实现的
2.Undo log 记录的是数据操作前的样子
3.redo log 记录的是数据被操作后的样子(redo log 是 Innodb 存储引擎特有)
4.bin log 记录的是整个操作记录(这个对于主从复制具备十分重要的意义)
从筹备更新一条数据到事务的提交的流程形容
1. 首先执行器依据 MySQL 的执行打算来查问数据,先是从缓存池中查问数据,如果没有就会去数据库中查问,如果查问到了就将其放到缓存池中
2. 在数据被缓存到缓存池的同时,会写入 undo log 日志文件
3. 更新的动作是在 BufferPool 中实现的,同时会将更新后的数据增加到 redo log buffer 中
4. 实现当前就能够提交事务,在提交的同时会做以下三件事
5.(第一件事)将 redo log buffer 中的数据刷入到 redo log 文件中
6.(第二件事)将本次操作记录写入到 bin log 文件中
7.(第三件事)将 bin log 文件名字和更新内容在 bin log 中的地位记录到 redo log 中,同时在 redo log 最初增加 commit 标记
至此示意整个更新事务曾经实现

总结

文章到这里就完结了,零碎是如何和 MySQL 数据库打交道,提交一条更新的 SQL 语句到 MySQL,MySQL 执行了哪些流程,做了哪些事件从宏观上都曾经解说实现了。

正文完
 0