@[toc]为什么要两阶段提交?不能一阶段提交工作吗?小伙伴们都知道MySQL中的事务是分两阶段提交的,我们看到的很多分布式事务也是分两阶段提交的,比如Seata,那么为什么要分两阶段提交呢?直接提交一次可以吗?今天就来聊聊这个话题。关于分布式事务seata,不了解的朋友可以参考松哥之前的文章传送门:五分钟带你体验一个分布式事务!太简单!看了那么多博客,还是不太懂TCC,那我们就来看看这个案例吧!XA的事情很深,小伙子,怕是抓不住!你是否为这个Saga交易保持“隔离”?1.什么是两阶段提交?1.1binlog和redologbinlogbinlog中文我们一般称之为archivedlogs。如果你之前看过松哥发的MySQL主从设置,应该对这篇日志有印象。我们在搭建MySQL主从时,会留下Donotopenbinlog(传送门:MySQL8主从复制踩坑指南)。Binlog是MySQLServer层的日志,不是存储引擎自带的日志。它记录了所有的DDL和DML(不包括数据查询语句)语句,并以事件的形式记录下来,包括语句执行所消耗的时间等,需要注意的是:binlog是一种逻辑日志,它记录SQL语句的原始逻辑,比如对某个字段+1。注意这里不同于redolog的物理日志(在某个数据页上对它做了什么修改)。binlog文件写满后,会自动切换到下一个日志文件继续写入,不会覆盖之前的日志。这也不同于重做日志。redolog是循环写入的,即后面的写入可能会覆盖前面的。进入。一般来说,我们在配置binlog的时候,可以指定binlog文件的有效期,这样过期后,日志文件会自动删除,避免占用更多的存储空间。根据MySQL官方文档,开启binlog后,会有1%左右的性能损失,但这还是可以接受的。一般来说,binlog有两个重要的使用场景:MySQL主从复制:在宿主机上启用Binlog,master将binlog同步到slave,slave通过binlog同步数据,从而实现master与从属之间的数据同步奴隶。MySQL数据恢复,使用mysqlbinlog工具结合binlog文件,可以将数据恢复到过去的某个时间点。我们之前提到的redolog的binlog是MySQL自己提供的,在MySQL的server层,而redolog不是MySQL提供的,是由存储引擎InnoDB自己提供的。因此,MySQL中有两种日志,binlog和redolog。这两类日志的存在既有历史原因(InnoDB最初并不是MySQL官方的存储引擎),也有技术原因。我们稍后再详细讨论这个问题。我们都知道事务的四大特性之一就是持久性,即只要事务提交成功,对数据库所做的修改就会永久保存并写入磁盘。如何才能做到这一点?其实我们很容易想到,每次事务提交时,都会将事务修改所涉及的所有数据页都刷新到磁盘中。一旦写入磁盘,就不用担心数据丢失。但是如果每次都这样,数据库就不知道有多慢了!因为Innodb是以页为单位与磁盘交互的,而一个事务可能只修改一个数据页中的几个字节,此时如果将完整的数据页刷入磁盘,不仅效率低下,而且是一种浪费的资源。效率低下是因为这些数据页在物理上并不连续,刷新数据页到磁盘涉及到随机IO。鉴于此,MySQL设计了重做日志,在重做日志中只记录事务对数据页做了哪些修改。那么有人说,写redolog不就是磁盘IO吗?而将数据写入磁盘也是磁盘IO。既然都是磁盘IO,为什么不直接把数据写到磁盘呢?付钱!这种说法很糟糕。写redolog和写数据有很大区别,就是redolog是顺序IO,而写数据涉及随机IO,写数据需要寻址,找到对应的位置,然后更新/添加/删除,而写redo日志在固定位置循环写入,是顺序IO,所以速度比写数据要高。重做日志本身分为:日志缓冲区(redologbuffer),这部分日志是volatile的。重做日志(redologfile),也就是磁盘上的日志文件,这部分日志是持久化的。MySQL每执行一条DML语句,先将记录写入redologbuffer,然后在某个时间点将多条操作记录写入redolog文件。这种先写入日志再写入磁盘的技术在MySQL中经常使用。说到WAL(Write-AheadLogging)技术(预写日志)。1.2两阶段提交在MySQL中,两阶段提交的主角是binlog和redolog。我们来看一张两阶段提交的流程图:从上图可以看出,事务最终提交的时候,分为三个步骤:写入redolog,处于prepare状态。写binlog。修改重做日志状态为commit。由于重做日志的提交分为准备和提交两个阶段,所以称为两阶段提交。2、为什么需要两阶段提交如果没有两阶段提交,那么binlog和redolog的提交无非是两种形式:先写binlog,再写redolog。先写redolog再写binlog。我们分别来看这两种情况。假设我们要向表中插入一条记录R。如果先写binlog再写redolog,那么假设binlog写完就crash了,redolog还没写完。那么在重启恢复的时候就会出现问题:binlog中已经有R的记录了。当slave从master同步数据或者我们使用binlog恢复数据时,都会同步到R的记录中;但是redolog中没有记录。关于R记录,崩溃恢复后,插入R记录的事务是无效的,即数据库中没有该行记录,造成数据不一致。相反,假设我们要向表中插入一条记录R。如果先写redolog再写binlog,那么假设redolog写完就crash了,此时binlog还没有写。那么在重启和恢复的时候就会出现问题:redolog中已经有R记录了,所以在crashrecovery之后,插入到R记录中的事务是有效的,数据是通过这条记录恢复到数据库中的;但是binlog中没有任何记录。R记录,所以slave从master同步数据时,或者我们使用binlog恢复数据时,不会同步到R记录,造成数据不一致。那么问题是不是可以按照上面说的两阶段提交来解决呢?我们来看以下三种情况:情况一:第一阶段commit后崩溃,即写入redolog,处于prepare状态时崩溃。此时:因为binlog还没有写入,redolog处于prepare状态,还没有提交。因此,当崩溃恢复时,事务将被回滚。此时binlog还没有写入,所以不会传输到备库。情况二:假设binlog写完就crash了,此时:redolog中的日志不完整,处于prepare状态,还没有提交,那么在恢复的时候,先检查binlog中的事务是否存在,是否存在complete,如果存在且Complete,直接提交事务,如果不存在或不完整,回滚事务。Case3:假设redolog在commit状态时crash,重启后的解决方案同Case2,可见两阶段commit可以保证数据的一致性。3.总结好了,今天和小伙伴们简单聊聊MySQL中的两阶段提交。如果您有任何问题,请留言讨论。
