转载~
当我们需要修改一个记录时,数据库会先根据条件找到要修改的数据,然后执行修改写入操作,因此我们再分析写操作的执行过程时,其实是包含读语句的执行过程的。
在之前《MySQL运行机制》文中,详细说明了一个查询语句的执行的过程,查询sql的执行过程基本上分为六步:
通过以上六步即可在数据库中查询到相应的数据,针对每个步骤的过程,这之前的文中有详细说明,这里不再赘述。
「查询缓存分为Cache和Buffer,两者都是缓存,但是作用不一样:」
「两者共性都属于内存,数据都是临时的,一旦关机数据都会丢失」。
查询缓存的弊端在《MySQL运行机制》文中未曾说明,这里做一下补充说明:
「大多数情况下一般不要使用查询缓存,因为查询缓存往往弊大于利」。
前面说到,更新操作时,也会走先查询,所以它的执行流程也是大同小异的。
还是通过这张图,按照【读语句的执行过程】的六个步骤去执行,不同的是:
「以上就是更新语句的执行过程,看起来似乎跟查询语句没啥区别,只不过一个查询语句,一个是更新语句,两者调用的存储引擎的接口不一样而已。事实上也的确如此,它们很类似,但是更新语句会比查询语句多两个步骤」。
上面说到更新语句会比查询语句多两个步骤,具体是多了什么呢?我们假设一下
按照上述的方式进行更新,似乎是没有问题的,数据也确实能写到数据库中,最终通过存储引擎写入磁盘中。但是有一个问题,我们知道「磁盘是很慢的,而我们的程序操作内存是需要IO操作的,当更新比较频繁的时候,磁盘IO必然会很慢,会降低数据库的性能,高并发下,很容易就会导致数据库宕机」。
既然有这种隐患,那么MYSQL不可能没有解决的,这里就涉及到了MySQL中两个非常重要的日志模块:
关于Undo log / Redo log与Binlog在之前的《一文详解六大日志》中也有详细的介绍,这里就不再具体描述,只重点说一下在更新操作的中使用过程。
如果对Undo log / Redo log与Binlog不是很熟悉的话,可以看一下之前的文章,以作参考
「撤消日志是在事务开始之前保存的被修改数据的备份,由InnoDB存储引擎实现」。
主要作用:
MVCC机制的实现原理在之前的《读懂MVCC多版本并发控制》 中已经详细描述,感兴趣的可以参考看一下。
「当执行一条更新语句的时候,InnoDB引擎会先把记录写到redo log里,并更新内存,到此更新操作就完成了,此时数据并没有写入磁盘,InnoDB会在特定的时机将记录写入磁盘中」。
我们知道「InnoDB的redo log是固定大小的,所以为了避免在刷盘之前redo log被写满,所以redo log采用的是循环写的方式」,如下:
write pos
:表示 redo log
当前记录的位置,一边写一边后移check point
:表示 「数据页更改记录」 刷盘后对应擦除的位置。write pos
到 check point
之间的部分是 redo log
空着的部分,用于记录新的记录;
check point
到 write pos
之间是 redo log
待落盘的数据页更改记录。
当 write pos
追上check point
时,会先推动 check point
向前移动(先刷盘,后擦除),空出位置再记录新的日志。
在MySQL系列的第一篇《架构体系》中的就已经阐述了基本架构组成包含Server层与存储引擎层,上述的「Redo log是InnoDB引擎所特有的一种日志,而MySQL支持的引擎是多种的,因此Server层也有自己的日志,即binlog(归档日志)」。
顾名思义:binlog日志只能用于归档, Redo log能够保证MySQL在任何时间段突然奔溃,重启后以前提交的记录都不会丢失,也就是「crash-safe」能力。
通过对以下的几篇文章的介绍,可以使我们对MySQL的写入有了一个大概的认识,内部的执行原理也有了比较清晰的认知,接下来看一下一条sql在执行的整个流程中,从它经历组件,各个组件做的操作等角度来分析一下写操作的执行过程,下面来看一下具体的写操作的执行过程。
update user set name='星河之码' where id=1;
来看看执行上述这个修改语句的整个过程,前面建立连接等几个步骤就省略了,直接看执行器执行时的过程,如下图:
通过以上执行过程分析图,写入操作就完成了,由此可见,虽然我们就写了一句update语句,但是实际上mysql还是帮助我们做了很多工作的。
对上图做了一个简化,其中比较重要的流程就是修改Buffer Pool 与日志同步的过程,如下图:
以上就是MySQ的InnoDB在写入的执行过程,其中涉及到很多细节,这里没有展开,比如数据页的读取,修改数据页之后Buffer Pool怎么刷脏,怎么保证Buffer Pool在有限的内存中加载到更多的热点数据,怎么提高Buffer Pool的命中率等,这些问题在以往的文章中都有详细介绍,有兴趣的可以看一下以下几篇文章:
上述的写操作执行过程中,写入Redo log的时候有两个阶段:「准备阶段与提交阶段」,为什么不直接一步到位,而要分了两步,再调用一下提交事务的接口呢?是否是多此一举呢?
实际上,在写Redo Log 与Binlog的时候采用「准备与提交」两个阶段的方式实现,是为了「保证数据一致性」。如果不用这个方式,而是两个日志都采用直接提交的方式,无论谁先谁后,都可能在数据路宕机时丢失数据导致不一致。
update user set name='星河之码' where id=1;
还是以这条更新语句来看,如下:
基于此,可以明确无论先写那个日志都会导致数据库不一致,因此,MySQL的设计了准备与提交的两阶段提交的方式。「Redo log和Binlog用于记录事物的行为状态,两阶段提交可以让这两个状态保持逻辑上的一致,以此保证数据的一致性」。