MYSQL 一个事务在提交的时候能够保证binlog和redo log是同时提交的,并且能在宕机恢复后保持binlog 和redo log的一致性。
先来看看什么是redo log 和binlog,以及为什么要保持它们的一致性。
什么是redo log,binlog
redo log
是innodb引擎层产生的日志, MYSQL从磁盘读取数据的单位是一页,当修改页中某条数据时,该行所在的数据页就变成了脏页
,由于脏页并不会立马刷新到磁盘,所以redo log会记录下数据页进行了哪些变动,用于服务崩溃时的数据恢复
。redo log是固定大小的,由多个文件组成一个环形的结构,
redo log由两个指针,write pos 和checkpoint,都是顺时针移动,write pos 记录redo log当前写入的位置, checkpoint往前移动,就代表就移动过的redo log记录的脏页刷新到磁盘上。所以,write pos
和checkpoint
之间的位置就代表redo log 还可以写的空间大小,当write pos等于checkpoint时,MYSQL则必须等待脏页刷新完毕后才能继续进行修改操作。
binlog
是mysql server服务层产生的日志。两者的用途也不一样。binlog
则主要用于数据库的备份,主从同步。binlog记录的是行变化,记录格式也有3种,statement,row,mixed,这里就不细讲了。
在了解了redo log 和binlog的含义和各自的作用后,我们先来看看它们在一次sql更新中是如何运作的。
sql 更新过程详解
来看下在一次事务过程中,它们的工作机制。假设我们在进行修改操作,那么可以用下面的流程图来表示,
1,首先判断要修改的数据是否在内存里,没有的话就从磁盘读取到内存。
2,写入redo log,注意这里写入的redo log仅仅是prepare状态,只有等到正式提交的时候才会变成commit状态。并且写入redo log也不是直接落盘,其实是写入到了redo log buffer
中,落盘时机受到innodb_flush_log_at_trx_commit
参数控制。
MYSQL会有一个后台线程,定时刷新redo log buffer 中的数据到磁盘上。除此以外,当innodb_flush_log_at_trx_commit 值为1
时 redo log则会在在prepare阶段将redo log buffer 中的数据落入磁盘。
注意📢📢📢,这里说的事务提交的时候redo log buffer中的数据刷到磁盘上,并不仅仅是执行的当前事务,比如A,B两个事务,A事务执行到一半,写了部分数据到redo log buffer,那么B此时提交事务,同样也会将A事务的redo log 刷到磁盘上。
当innodb_flush_log_at_trx_commit 值为 0
时,redo log buffer则不会在prepare或者事务提交时刷盘,而是由后台定时任务定时刷新redo log buffer中的数据到磁盘上。
当innodb_flush_log_at_trx_commit 值为2时
,则是将redo log buffer中的内容刷新到文件系统缓存中,由操作系统决定何时刷新到磁盘上。
所以可以看到,在事务执行过程中,redo log是可能一部分在内存,一部分已经落入磁盘了
3, 在写完redo log后,会去写binlog,写binlog同样不是直接写文件,而是写到binlog cache中,那么binlog是何时刷新到磁盘上呢,这个是由sync_bin
参数决定的。
-
sync_binlog = 0
:提交事务时,将内存中的binlog cache写到文件系统缓存中,后续交由操作系统决定何时将数据持久化到磁盘 -
sync_binlog = 1
:提交事务时,将binlog cache中的数据写入到文件系统缓存,并立马刷新到磁盘。 -
sync_binlog =N(N>1)
:提交事务时,都写到文件系统缓存,但累积 N 个事务后才 fsync 刷新到磁盘。
4, 最后一步便是对事物进行提交,按参数设置分别对redo log和binlog进行落盘处理。
为什么要保证binlog 和redo log 同时提交
看完了整个sql更新过程,先说下结论,将innodb_flush_log_at_trx_commit
和 sync_binlog
都设置为1 能够保证binlog 和redo log 同时提交。
再来看看如果redo log和binlog不同时提交会导致什么问题❓
redo log和binlog不同时提交会导致
主备不一致
如果在一个事务提交过程中, binlog写入成功了,此时主库宕机,redo log写入失败,主库恢复后,那么binlog可能就会被从库拿去执行,然而主库的redo log是没有修改数据的,所以造成主备不一致。
换过来,redo log写入成功,但是binlog提交失败,从库就会缺失新的修改数据,造成主备不一致。
两阶段提交避免数据不一致
接着,我们来细聊 MYSQL在上述sql更新过程中,是如何保证redo log和binlog是同时提交的。
上述事务执行过程中,可以看到对于redo log的提交分了两个阶段,第一个是redo log的prepare
阶段,第二个是commit
阶段。
宕机恢复时,redo log执行恢复的逻辑概括如下,
1,只要redo log变成了commit状态,MYSQL就认为事务是成功了。
2,而恢复时,发现redo log是prepare 状态的话,就会去判断对应事务的binlog 是否完整,完整则对还未提交的事务进行提交,不完整则回滚事务。
我们来分析下异常的情况:
如上图所示,
1,在第一种异常情况下,redo log 和binlog都没有写入,主备是一致的。
2,第二和第三种异常情况, redo log
已经落入磁盘,最后就看binlog是否完整了,完整宕机恢复后进行事务提交,备库即使得到binlog,也能保证与主库恢复后事务提交的数据 保持一致。
📢📢📢需要注意的是,innodb_flush_log_at_trx_commit 为1
时才能保证redo log是在binlog写入前是已经落盘的,如果是0或者2,则有可能出现节点崩溃时,redo log没有写入到磁盘而丢失,而binlog是完整的情况,造成主备不一致。
两阶段提交带给业务开发上的思考🤔
从MYSQL 实现两阶段提交的逻辑,可以归纳下,它是如何做到对两个业务做到最终一致的。
我举个业务上的例子, 比如有A,B两个服务,A服务依赖B服务,如何保证在A服务上的数据操作和请求B服务接口这两个动作同时成功或失败❓
我直接说下结论,
借鉴两阶段提交的逻辑,我们可以将A服务的数据操作在业务设计上增加一个预扣减的概念,先锁定A服务数据资源,然后去请求B服务的接口,失败的话,则释放A服务锁定的数据资源,成功的话则进行真实的扣减。
除此以外,还需要增加一个对A服务数据进行补偿修复的定时任务
,类似与MYSQL数据库宕机根据binlog是否完整看事务是否提交一样,定时任务定期查看还没有终结的A服务数据,拎出来请求B服务查看业务成功状态,B服务返回成功,则将A服务的业务数据进行真实扣减,否则释放A服务锁定的数据资源。
通过两阶段提交,来查看业务的最终一致性。
最后,
自荐一波✅:
欢迎朋友们关注我的公众号📢📢:【蓝胖子的编程梦】!
学习容器知识🐳,性能监控🚀,Golang🐋 相关编程知识