MySQL作为为现下最流行的开源数据库之一,其中一个重要的原因就是其能保证数据的可靠性,即可以保证数据不丢失。本文对MySQL保证数据不丢失的原理知识进行了总结。本文的内容都是在MySQL的innodb引擎下的原理
1.一条sql语句在MySQL中的执行过程理解MySQL数据不丢失原理首先要理解一条sql语句在MySQL中是如何执行的。
总体上,MySQL的逻辑架构可以分为Server层和存储引擎两层。
Server层的主要负责处理MySQL的核心服务功能,以及一些内置函数处理等等。
存储引擎层则主要负责数据的存储和提取。支持多种存储引擎。比如最常用的InnoDB。
sql语句大致可以分为两种,查询语句和更新语句。
(1)查询语句执行过程:
其大致流程如下图所示:
流程:
Step1 :客户端与MySQL进行连接,负责这个功能就是Server层的连接器。这一步主要做的是验证客户端的身份,权限等。
Step2:查询缓存,即从缓存中看看这个语句之前是不是执行过,如果执行过,且缓存中有数据,则直接返回数据给客户端。(MySQL8.0以上版本不支持查询缓存,MySQL8.0版本以上可以认为没有这个步骤,缓存技术一般由非关系数据库实现,比如redis,不应该是数据库考虑的)
Step3:没有命中缓存,则分析语句,准备开始执行,sql是与数据库沟通用的语言,分析器就是解析sql语言,明白客户端需要什么数据。当然,这里也有可能sql存在语法错误,出现语法错误则返回语法错误信息。
Step4:经过分析器,则可以认为MySQL已经了解客户端需要查询的内容,这个时候进入优化器,优化器的作用就是为选择合适查询方式。(比如:走哪个索引合适,连接表的顺序等等优化 *** 作)。
Step5:语句进入执行阶段,调用存储引擎查询数据(这个过程还会再核对一次当前客户端的权限,如果对该表没有查询权限,就会返回错误信息),引擎将查询出来的结果组成结果集返回给客户端。
(2)更新语句执行流程:
更新语句执行流程其实与查询语句执行流程差不多,因为更新过程一样要先定位到该数据,这个过程就和查询语句的流程是差不多,依次需要经过连接器->分析器->优化器->执行器->存储引擎找到该数据项,然后更新。与查询流程最大的不同的地方就是,更新过程中涉及到两个日志模块,分别是重做日志(redo log)和归档日志(binlog)。这两个日志模块是MySQL保证数据不丢失的重要模块。
2.重做日志(redo log)
每次更新 *** 作如果都要直接写进磁盘中,则每一次的更新 *** 作都要先到磁盘找到该行数据,然后进行更新, *** 作磁盘则就涉及到IO *** 作,每个更新 *** 作都要进行IO *** 作成本将很大。所以MySQL引入了redo log来提升更新效率。即WAL(Write-Ahead Logging)技术,其核心就在于每次更新 *** 作都是先写日志,等空闲或者redolog写不下的时候再去更新磁盘。
即有了redolog后,更新流程变为,每次更新 *** 作,将记录写在redolog中,并更新内存中的数据,然后直接返回修改成功。然后到合适的时候将redolog中的内容更新到磁盘中。(这样做将极大的提高更新的效率,客户端可以在较短的时间内得到反馈,而且存储引擎不再需要为每一次的更新 *** 作都进行IO *** 作,可以存到一定的“量”,然后再刷新进磁盘里)。
redolog是固定大小的,写完则重头开始写,MySQL通过两个指针来控制redolog中的内容,分别是checkPoint(当前更新进磁盘的位置),writePoint(当前已经写到的位置),两个指针右移到末尾则又重新开始(checkPoint将指向的内容刷新进磁盘并右移,writePoint将新的更新 *** 作写入并右移),显然,writePoint和checkPoint之间的位置就是可写位置,如果writePoint == checkPoint,说明redolog满了,这个时候就需要停下来将redolog中的内容刷新进磁盘里(这个时候MySQL的更新 *** 作会变慢),让checkPoint向右移动,从而“腾出位置”继续写。
redolog的存在不仅提高了更新的效率,还保证数据的不丢失,如果MySQL发生异常重启,则可以加载redolog文件恢复数据。
3.归档日志(binlog)
binlog是Server层的日志文件,binlog与redolog,都是用来记录对数据做了什么修改,,但是有一定的区别:
记录的内容上:redolog是物理日志,记录的是具体在哪一个数据页(数据页是MySQL中磁盘和内存交换的基本单位,也是MySQL管理存储空间的基本单位,可以理解为一个数据页中有多行数据)做了什么修改,而binlog记录的更新语句的原始逻辑,比如在id=XXX的a字段上+1.
记录方式上:redolog是循环写,有固定的大小,binlog是追加写,一个文件用完切换到下一个,不会覆盖之前的数据。
实现的位置:redolog是存储引擎层实现的(innodb特有的一种日志),binlog是server层的日志,所有引擎都具有。
整合两个日志,重新梳理一下更新过程:
step1:建立连接(连接器)
step2:分析sql(分析器)
step3:优化过程,选择合适的索引来定位数据等等(优化器)
step4:调用引擎层接口去拿数据(执行器)
step5:存储引擎找到这一行,如果这一行所在的数据页就在内存中,直接返回这行数据,没有则从磁盘中读入内存,然后返回。
step6:执行器拿到数据,对该数据进行对应的修改,得到一行新的数据,然后调用引擎接口写入这行新数据。
step7:存储引擎直接将数据更新在内存中,并将这个 *** 作更新到redolog中,redolog此时处于prepare状态。然后直接返回,表明随时可以提交。
step8:执行器将这个 *** 作更新到binlog中,并更新进磁盘。
step9:执行器再次调用引擎接口,让引擎将刚刚的更新 *** 作进行提交(redolog改成commit状态),整个更新过程完成。
为什么这样就能保证数据不丢失呢?或者说为什么redolog要分为prepare和commit两个状态(即两阶段提交)?
首先,如果redolog不分两个阶段,直接在step7直接提交,那么假如MySQL在执行step8的时候发生异常重启,则恢复过来后,这个更新 *** 作是再redolog中是已提交的,则这个修改是最终会成功刷新进磁盘的,但是binlog中没有对应的记录,这将导致如果要使用binlog将数据库回退到某一个时刻的时候,“漏”了一条sql更新记录而导致数据回退的与那个时刻并不一致,或者做主从一致(通过binlog实现主从一致)时,从库从主库中拿到的binlog是“少”了一条sql记录的,这将导致主从数据不一致。(其实可以直接理解为binlog没有记录MySQL中的所有更新 *** 作本身就是违背了binlog的设计的初衷)。
其次,如果先提交binlog也是不行的,如果先提交了binlog,再提交了redolog,提交redolog过程中发生异常重启,redolog中的数据没有提交从而丢失,则binlog则是“多”了一条数据 *** 作。
但是如果是两阶段提交则能有效规避这些问题,比如step8中binlog提交过程发生异常,则redolog中记录的是出于prepare状态的,等MySQL恢复过来时,则认为这次更新是失败的,则会自动回滚这次 *** 作。这样binlog中更新记录与数据库中的数据是可以对应上的。
最后,MySQL保证了binlog和redolog中的数据更新记录与真实数据 *** 作是一致的,就可以保证数据不丢失(所有的更新 *** 作都记录了,可以恢复到任意时刻),在做主从备份或者数据库备份时往往都是依靠binlog进行,所以关键便在于保证binlog的数据与数据库 *** 作保持一致。(MySQL在保证数据不丢失上还有很多细节,这将在后续继续总结)。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)