链接:
jishuin.proginn.com/p/763bfbd67…
redo log
redo log 包括两个部分,一个是内存中的redo log buffer,另一个是磁盘上的redo log file。
当mysql每执行一条DML语句,就将记录写到 redo log buffer,后续某个时间点再一次性将多个记录写到redo log file中,这种先写buffer,再写磁盘的技术就是MySQL中的WAF(Write-Ahead Logging)技术。
- 更新buffer pool中页中的数据
- 生成一个redo log
- commit,持久化这个redo log
为什么要多一个持久化redo log的过程而不是在commit时候,直接持久化buffer pool里面的页数据呢?
因为设计到了随机IO和顺序IO。
有以下两种情况:
- 只修改了页中一条数据,但是持久化这个页却要操作整个页,额外操作了很多数据
- 页里的数据只是逻辑上是顺序的,而在磁盘上却不是连续的,则这些数据写的时候属于
随机IO
,速度肯定会慢
MySQL提前在磁盘开辟了多个连续空间,这些每个连续空间就是一个redo log file,所以持久化redo log的时候,就是顺序IO
,速度肯定会快些.
redo log file采用了循环写的方式,redo log大小和数量可以配置,从第一个文件写,写满最后一个文件后(就是write pos追上check point时),会触发脏数据持久化,并推动check point向前移动,此时写入不了新数据
在操作系统中,用户空间下缓冲区数据一般无法直接写到磁盘里,中间必须经过操作系统内核空间缓冲区(OS Buffer)。所以,redo log buffer写入redo log file实际上是先写入OS Buffer,再通过系统调用fsync()将数据刷到redo log file.
MySQL有三中将redo log buffer写入redo log file的方式,可以通过参数innodb_flush_log_at_trx_commit
配置,详情如下:
参数值 | 含义 |
---|---|
0 | 事务提交时不会将 redo log buffer 中日志写入到 os buffer ,而是每秒写入 os buffer 并调用 fsync() 写入到 redo log file 中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。 |
1 | 事务每次提交都会将 redo log buffer 中的日志写入 os buffer 并调用 fsync() 刷到 redo log file 中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。 |
2 | 每次提交都仅写入到 os buffer ,然后是每秒调用 fsync() 将 os buffer 中的日志写入到 redo log file 。 |
注意: 方式2的话,就算MySQL crash了,OS Buffer的数据还在,因为MySQL crash影响不到OS Buffer
binlog
首先要知道什么是 crash-safe
?
crash-safe
只MySQL宕机重启后,能保证
- 所有已经提交的事务的数据仍然存在
- 所有未提交的事务的数据自动回滚
以前MySQL
只有MyISAM
引擎,但是MyISAM
没有crash-safe
的能力,并且binlog
只能用于归档。后来出现了InnoD
B引擎,并且带有redo log
日志,而binlog
和redo log
结合,就可以实现crash-safe
,并且通过两阶段提交
来保证两个日志的一致性
redo log和bin log区别
redolog | bin log | |
---|---|---|
文件大小 | 固定,可以配置 | 可以配置 |
实现方式 | InnoDB引擎实现的 | binlog是MySQL Server层实现的,所有引擎都可以用 |
记录方式 | 循环写 | 追加写入,到达一定大小切换到下一个继续写,不会覆盖之前的数据 |
适用场景 | 崩溃恢复(crash-safe) | 主从复制和数据恢复 |
两阶段提交
假设有以下语句:
update table set a=1 where id =1;
流程如下:
可看到共三个步骤:
- 写入redo log,处于prepare状态
- 写入bin log
- 修改redo log状态为commit
redo log
分为prepare
和commit
状态,所以称为两阶段提交
为啥要两阶段提交?
假设a
的值原来是100
,update
写完第一个日志后,数据库就crash
了,会出现以下情况:
- 先写
redo log
,bin log
还没有写,MySQL
宕机重启,而通过redo log
可以把数据恢复过来,所以恢复后,a
的值为1
.但是由于bin log还没来得及写,所以bin log
中没有记录update
这个语句,而以后在别处用这个bin log
恢复数据库时候,会缺少了一次更新,恢复出来a
的值是之前的100
. - 先写
bin log
,redo log
还没写就宕机重启,导致这个事务无效,所以数据库重启后恢复数据完,a``的值还是100
.但是以后用bin log
来加载备份数据时候,a
的值却是1
,相当于bin lo
g多了一条更新语句
两阶段提交的主要用意是:为了保证redolog和binlog数据的安全一致性。只有在这两个日志文件逻辑上高度一致了。你才能放心地使用redolog帮你将数据库中的状态恢复成crash之前的状态,使用binlog实现数据备份、恢复、以及主从复制。而两阶段提交的机制可以保证这两个日志文件的逻辑是高度一致的。没有错误、没有冲突。
那么崩溃恢复如何完成呢?
如下图:
- 如果
redo lo
g里面的事务是commit
的,则直接提交 - 如果
crash
在时刻A
,由于redo log
未提交,bin log
还没写入,这个事务会回滚 - 如果
crash
在时刻B
,则会判断bin log
中对应的事务是否存在,存在则提交,否则回滚
一定需要两阶段提交吗?
bin log
默认是不开启的,因为如果你不需要bin log
的特性(数据备份恢复,主从同步
),就不需要两阶段提交,就用不到bin log
了。
只用redo log
就可以在crash
后将MySQL内存中的数据恢复到crash之前的状态