Redo Log重放日志
保证数据不丢失
Redo Log是InnoDB存储引擎提供的能力。
当MySQL提交事务时,数据不会立刻写入磁盘,因为磁盘IO非常昂贵,直接写磁盘性能太低。
为了提高性能,会先将操作记录写到redo log,然后在适合的时机再将更新的数据真正写入磁盘。这种先写日志,再写磁盘的方式叫做WAL(Write-Ahead Logging)
。
那么什么时候将表数据写入磁盘呢?
redo log的模型大致模型如下图所示,多个redo log文件组成一组,假设每组4个文件,每个文件大小固定为1G。
可以通过innodb_log_file_size
修改每个文件大小,使用innodb_log_files_in_group
修改一组redo log文件数量。
在一组redo log文件组成的环形结构上,有一个指针叫做write pos
,用于记录当前写到什么位置,随着log的写入,该指针不断向后移动。
由于redo log文件大小是固定的,因此在快写满之前,要擦除旧的log记录,check point
指针就用于记录要擦除的位置。
而就是在要擦除的时候,将redo log所记录数据更新操作真正的更新到磁盘数据文件中
有了Redo Log后,无论MySQL服务是正常停止还是崩溃,当MySQL服务启动时,都会检查Redo Log和磁盘上的数据页的LSN(log sequence number)。
当发现磁盘数据LSN小于Redo Log的LSN,则说明有些数据还未真正写入磁盘,则根据Redo Log以恢复数据,从而保证了数据不丢失。
Redo Log写入过程
存储引擎在写redo log时,会先写入内存的redo log buffer
缓冲区(innodb_log_buffer_size
参数配置),然后再在合适的时间点写入OS Buffer,并调用fsync()
真正写入磁盘上的redo log中。
日志从redo log buffer
写入磁盘的整个流程大致如下:
什么是合适的时间点呢?MySQL中提供了一个参数|可以配置这个时间点
参数值 | 说明 |
---|---|
0 | 事务提交时redo log留在redo log buffer中。由后台线程定时每秒将redo log buffer写入OS Buffer,再调用fsync 写入磁盘redo log。因此可能丢失1秒的数据 |
1(推荐,默认) | 事务提交就将redo log buffer写入OS Buffer,再立即调用fsync 写入磁盘redo log。(实际上事务过程中就写入磁盘)不会丢数据 |
2 | 事务提交就将redo log buffer写入OS Buffer,定时每秒调用fsync 写入磁盘redo log |
看到这里,也许你会有疑问,同样都是写磁盘,为什么写redolog性能就高,写表数据性能就差呢?
因为Redolog是顺序IO,并且可以组提交,而写表数据是随机IO,性能较差。
不过有三种情况下,即使事务未提交,redo log也可能被写入磁盘中:
- 后台线程每秒定时任务,将redo log buffer写入磁盘
- redo log buffer占用空间即将达到
innodb_log_buffer_size
一半,后台线程会主动将redo log写入OS Buffer,但不会真正写入磁盘 - 另外一个并发的事务提交时,把整个redo log buffer中的日志都写入磁盘,其中可能就包含了未提交的事务的redo log
组提交
每一条redo log都有一个递增的逻辑序列号LSN(log sequence number),它的其中一个作用就是前面提到的:
当发现磁盘数据LSN小于Redo Log的LSN,则说明有些数据还未真正写入磁盘,则根据Redo Log以恢复数据,从而保证了数据不丢失
可以看出,redo log所对应的数据其实也是带有相同LSN的,这也可以防止同一条redo log被重复执行。
除此之外,InnoDB为了优化redo log写入的性能,利用LSN实现了组提交的方式。
假设此时有A事务B事务,它们的redo log 的LSN分别是50,100。那么组提交的过程如下:
- 两个事务在提交过程中,假设A事务带着LSN为50的redo log先进入,则会被选为组长
- 事务A准备写磁盘前,B事务也来了,LSN为100。
- 事务A带着LSN=100及以下所有的redo log进行写盘
- 同时事务B进行等待
- 事务A写完磁盘返回时,B事务也可以直接返回
从上面过程中可以看出,事务A和事务B的redo log作为同一个组一起提交了,且真正写盘动作只有一次,由事务A完成,减少了磁盘IO带来的性能开销。
一个组中的事务越多,组提交所带来的好处越明显
Binlog归档日志
binlog是MySQL的归档日志,常用于数据的备份与恢复,主从复制等
可以通过修改配置文件开启:
[mysqld]
server-id=1 # 据说不设置server-id的话会有bug。
log-bin=mysql-bin
开启之后,binlog日志将记录所有的SQL增删改操作。
binlog日志格式主要有三种:
- statement:记录执行的SQL语句
- raw:记录数据更新前和更新后的内容
- mixed:混合前面两种,mysql会根据执行语句的情况动态选择一种合适的格式记录
可以修改配置文件,如binlog-format="RAW"
有了binlog之后,就能将数据库恢复到任意一个时刻。
关于数据库的备份与恢复,通常采用的方法是先定期备份整个数据库,比如一周备份一次,当你想要恢复数据到某个时刻时,先将数据恢复到最近的一次备份,然后再通过binlog日志从备份点开始执行到你想要的时间点。
Binlog写入过程
binlog的写入过程和redo log有些类似。在事务中写binlog时,会先将log先入到binlog cache
,在事务结束时再将binlog写入系统的OS Buffer,调用fsync()
真正写入磁盘。一个事务中如果有多条binlog,将会一起写入磁盘,不会一条条写。
大致流程如下:
但binlog和redo log有个很大的区别:MySQL中每个线程都有一块Binlog Buffer内存空间,而Redo Log Buffer是全局共享一块内存空间。
因为一个事务里的所有binlog必须连续且全部成功,不能被打断,因此每个线程都要有一块Binlog Buffer。
Binlog Buffer大小可以使用参数binlog_cache_size
配置,和redo log类似的,binlog的写入时机也是有参数可以配置的:sync_binlog
参数值 | 说明 |
---|---|
0 | 提交事务时只写入OS Buffer |
1 | 每次事务提交都会真正写入磁盘 |
N | 每次事务提交都将binlog写入OS Buffer,累计N个事务后写入磁盘 |
该参数值为0时风险较大,如果操作系统崩溃重启,部分还在OS Buffer中未落盘的binlog将丢失。
设为1是最为安全的,不过性能相对较差。适合数据安全要求性高,磁盘性能优越的场景。
N(常设置为100~1000)值性能较好,不过和0类似,操作系统重启也可能丢失最近N个事务的binlog日志
下面是redo log 和binlog 两个参数推荐组合:
innodb_flush_log_at_trx_commit | sync_binlog | 说明 |
---|---|---|
1 | 1 | 适合数据安全性要求高,磁盘性能优越 |
1 | 0 | 适合数据安全性要求高,磁盘性能不足,允许从节点延迟 |
2 | N | 适合数据安全性要求低,允许丢失部分事务,允许从节点延迟 |
0 | 0 | 磁盘性能差,允许从节点延迟 |
以上,推荐两个值都设置为1
两阶段提交
开启binlog后,mysql更新数据的整个流程又有了些许变化:
- InnoDB会先更新内存中的数据,同时将这个操作记录写到redo log,且该记录的状态为prepare
- 接着执行器生成这个操作的binlog,并把binlog写入磁盘
- 最后提交事务,InnoDB会将redo log的状态改为commit
大致流程图如下:
仔细看这个流程,你会发现redo log一开始的状态是prepare,最后写完binlog且事务提交的时候将redo log状态设置为commit。这个过程叫做两阶段提交
。
那么为什么需要两阶段提交呢?
假设执行上面流程过程中,写binlog时崩溃了,由于redo log已经写下了,那么服务再次启动时就会根据redo log恢复数据。
但是binlog日志缺少了这条数据,将会导致严重后果:
- 进行主从复制时从节点就会少一条数据更新,主从不一致
- 将来利用binlog日志来进行数据时光旅行时,也会少一条数据更新。
为了解决上述问题,必须使用两阶段提交。
有了两阶段提交,mysql在崩溃后恢复数据时:
- 如果redo log状态是commit,则事务有效,恢复数据
- 如果redo log状态是prepare,则查看binlog日志
- 如果binlog没有这条数据,表示该事务无效,数据无效。否则就会导致上面说的问题
- 如果binlog有这条数据,事务有效,恢复数据
简单来说就是redo log和binlog都有记录的数据才是真正有效的数据