Undo Log、Redo Log 和 Binlog
# Undo Log、Redo Log 和 Binlog
Undo Log、Redo Log是InnoDB特有的日志文件,主要负责实现事物的ACID特性,例如处理事物回滚、服务崩溃恢复期间更正不完整事务写入的数据等,而Binlog是早期MySQL就存在的日志文件,主要作用是数据恢复、主从同步。
# Undo Log
Undo:意为撤销或取消,以撤销操作为目的,返回指定某个状态的操作。
Undo Log:数据库事务开始之前,会将要修改的记录存放到 Undo日志里,当事务回滚时或者数据库崩溃时,可以利用 Undo日志,撤销未提交事务对数据库产生的影响。
# 生命周期
undo log在事务开始前产生;事务在提交时,并不会立刻删除undo log,InnoDB会将该事务对应的undo log放入到删除列表中,后面会通过后台线程purge thread
进行回收处理。Undo log属于逻辑日志,记录一个变化(的相反)过程。例如执行一个delete,undo log会记录一个insert;执行一个update,undo log会记录一个相反的update。
# 存储
undo log采用段的方式管理和记录。在innodb数据文件中包含一种rollback segment
回滚段,内部包含1024个undo log segment
。可以通过下面一组参数来控制Undo log存储。
show variables like '%innodb_undo%';
# 作用
- 实现事务的原子性:Undo Log 是为了实现事务的原子性而出现的产物。事务处理过程中,如果出现了错误或者用户执行了 ROLLBACK 语句,MySQL 可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。
- 实现多版本并发控制(MVCC):Undo Log 在 MySQL InnoDB 存储引擎中用来实现多版本并发控制。事务未提交之前,Undo Log保存了未提交之前的版本数据,Undo Log 中的数据可作为数据旧版本快照供其他并发事务进行快照读。
例如下面的例子:
- 事务A手动开启事务,执行更新操作,首先会把更新命中的数据备份到 Undo Buffer中。
- 事务B手动开启事务,执行查询操作,会读取 Undo 日志数据返回,进行快照读
# Redo Log
Redo:顾名思义就是重做。以恢复操作为目的,在数据库发生意外时重现操作。 Redo Log:指事务中修改的任何数据,将最新的数据备份存储的位置(Redo Log),被称为重做日志。
# 生命周期
随着事务操作的执行,就会生成Redo Log,在事务提交时会将产生Redo Log写入Log Buffer,并不是随着事务的提交就立刻写入磁盘文件。等事务操作的脏页写入到磁盘之后,Redo Log 的使命也就完成了,Redo Log占用的空间就可以重用(被覆盖写入)。
# 工作原理
Redo Log 是为了实现事务的持久性而出现的产物。防止在发生故障的时间点,尚有脏页未写入表的 IBD 文件中,在重启 MySQL 服务的时候,根据 Redo Log 进行重做,从而达到事务未入磁盘数据进行持久化这一特性。
# 写入机制
Redo Log 文件内容是以顺序循环的方式写入文件,写满时则回溯到第一个文件,进行覆盖写。
如图所示:
write pos
是当前记录的位置,一边写一边后移,写到最后一个文件末尾后就回到 0 号文件开头;checkpoint
是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件;
write pos
和 checkpoint
之间还空着的部分,可以用来记录新的操作。如果 write pos
追上checkpoint
,表示写满,这时候不能再执行新的更新,得停下来先擦掉一些记录(同步数据到磁盘),把 checkpoint
推进一下。
Redo Log写满的时候,可能会导致我们SQL语句突然的执行缓慢:如果数据库一直很忙,更新又很频繁,这个时候 redo log又很快就会被写满了,这个时候就没办法等到空闲的时候再把数据同步到磁盘,只能暂停其他操作,全身心来把数据同步到磁盘中去的,而这个时候,就会导致我们平时正常的SQL语句突然执行的很慢。
# 相关配置参数
每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组至少有2个重做日志文件,默认为ib_logfile0
和ib_logfile1
。可以通过下面一组参数控制Redo Log存储:
show variables like '%innodb_log%';
Redo Buffer 持久化到 Redo Log 的策略,可通过 Innodb_flush_log_at_trx_commit
设置:
- 0:每秒提交 Redo Buffer -> OS cache -> flush cache to disk,可能丢失一秒内的事务数据。由后台Master线程每隔 1秒执行一次操作。
- 1(默认值):每次事务提交执行 Redo Buffer -> OS cache -> flush cache to disk,最安全,性能最差的方式。
- 2:每次事务提交执行 Redo Buffer -> OS cache,然后由后台Master线程再每隔1秒执行OS cache -> flush cache to disk 的操作。
一般建议选择取值2,因为 MySQL 挂了数据没有损失,整个服务器挂了才会损失1秒的事务提交数据。
# Binlog
Redo Log 是属于InnoDB引擎所特有的日志,而MySQL Server也有自己的日志,即 Binarylog(二进制日志),简称Binlog。
Binlog是记录所有数据库表结构变更以及表数据修改的二进制 日志,不会记录SELECT和SHOW这类操作。Binlog日志是以事件形式记录,还包含语句所执行的消耗时间。开启Binlog日志有以下两个最重要的使用场景。
- 主从复制:在主库中开启Binlog功能,这样主库就可以把Binlog传递给从库,从库拿到Binlog后实现数据恢复达到主从数据一致性。
- 数据恢复:通过
mysqlbinlog
工具来恢复数据。
# 记录模式
Binlog文件名默认为“主机名_binlog-序列号”格式,例如xxx_binlog-000001
,也可以在配置文件中指定名称。文件记录模式有STATEMENT、ROW和MIXED
三种,具体含义如下:
ROW(row-based replication, RBR):日志中会记录每一行数据被修改的情况,然后在slave端对相同的数据进行修改。
优点:能清楚记录每一个行数据的修改细节,能完全实现主从数据同步和数据的恢复。
缺点:批量操作,会产生大量的日志,尤其是
alter table
会让日志暴涨。STATMENT(statement-based replication, SBR):每一条被修改数据的SQL都会记录到master的Binlog中,slave在复制的时候SQL进程会解析成和原来master端执行过的相同的SQL再次执行。简称SQL语句复制。
优点:日志量小,减少磁盘IO,提升存储和恢复速度
缺点:在某些情况下会导致主从数据不一致,比如
last_insert_id()
、now()
等函数。MIXED(mixed-based replication, MBR):以上两种模式的混合使用,一般会使用STATEMENT模式保存Binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存Binlog,MySQL会根据执行的SQL语句选择写入模式。
# 文件结构
MySQL的Binlog文件中记录的是对数据库的各种修改操作,用来表示修改操作的数据结构是Log Event。不同的修改操作对应的不同的log event。比较常用的log event有:Query event、Row event、Xid event等。Binlog文件的内容就是各种Log event的集合。
Binlog文件中Log event结构如下图所示:
# 写入机制
Binlog主要是以事件触发追加的方式写入。
根据记录模式和操作触发event事件生成log event(事件触发执行机制);
将事务执行过程中产生log event写入缓冲区,每个事务线程都有一个缓冲区;
Log Event保存在一个
binlog_cache_mngr
数据结构中,在该结构中有两个缓冲区,一个是stmt_cache
,用于存放不支持事务的信息;另一个是trx_cache
,用于存放支持事务的信息(binlog被所有存储引擎共用的机制,存储引擎不一定支持事物)。事务在提交阶段会将产生的log event写入到外部binlog文件中。
不同事务以串行方式将log event写入binlog文件中,所以一个事务包含的log event信息在binlog文件中是连续的,中间不会插入其他事务的log event。
# 相关操作
Binlog状态查看
show variables like 'log_bin';
开启Binlog功能
# 错误的开启方式
mysql> set global log_bin=mysqllogbin; ERROR 1238 (HY000): Variable 'log_bin' is a read only variable
需要修改my.cnf
或my.ini
配置文件,在[mysqld]下面增加如下配置并重启MySQL服务:
# log-bin=ON
# log-bin-basename=mysqlbinlog
binlog-format=ROW
log-bin=mysqlbinlog
server-id=1
使用show binlog events命令
show binary logs; // 等价于show master logs;
show master status;
show binlog events;
show binlog events in 'mysqlbinlog.000001';
注意,下面使用mysqlbinlog
相关命令需要推出MySQL客户端交互命令。
使用mysqlbinlog命令
mysqlbinlog "文件名"
mysqlbinlog "文件名" > "test.sql"
使用 binlog 恢复数据
# 按指定时间恢复
mysqlbinlog --start-datetime="2022-03-18 18:00:00" --stop- datetime="2022-03-18 19:00:00" mysqlbinlog.000002 | mysql -uroot -p
# 按事件位置号恢复
mysqlbinlog --start-position=9500 --stop-position=9527 mysqlbinlog.000002 | mysql -uroot -p
mysqldump:定期全部备份数据库数据。mysqlbinlog可以做增量备份和恢复操作。
删除Binlog文件
# 删除指定文件
purge binary logs to 'mysqlbinlog.000001';
# 删除指定时间之前的文件
purge binary logs before '2020-04-28 00:00:00';
# 清除所有文件
reset master;
可以通过设置expire_logs_days
参数来启动自动清理功能。默认值为0,表示没启用。设置为1表示超出1天binlog文件会自动删除掉。
# 相关配置参数:
# binlog刷盘策略,默认0,表示不控制binlog的刷新,由文件系统自己控制。性能最好,但是一旦Crash,binlog_cache信息丢失
# > 0,表示每x次事务提交,MySQL调用文件系统的刷新操作将缓存刷下去,值为1时最安全,但是性能损耗最大。通常设置为100或者是0
sync_binlog=0
# 主从同步忽略的库
binlog-ignore-db=db_name
# 指定同步的库
--binlog-do-db=db_name
# Redo Log & Binlog
Redo Log和Binlog是MySQL日志系统中非常重要的两种机制,也有很多相似之处(都有恢复的意味),下面再总结一下两者的区别:
# 两者区别
- 归属不同:Redo Log是属于InnoDB引擎功能,Binlog是属于MySQL Server自带功能,并且是以二进制文件记录。
- 日志属性不同:Redo Log属于物理日志,记录该数据页更新状态内容,Binlog是逻辑日志,记录更新过程。
- 写入机制不同:Redo Log日志是循环写,日志空间大小是固定,Binlog是追加写入,写完一个写下一个,不会覆盖使用。
- 作用不同:Redo Log作为服务器异常宕机后事务数据自动恢复使用,Binlog可以作为主从复制和数据恢复使用。Binlog没有自动
crash-safe
能力。
# 为什么要用两个日志模块
我们知道,Redo Log、Binlog都可以实现持久性,那么为什么MySQL非要用两个日志模块呢?(注意,Undo Log是事物原子性的产物,别把它与上面两者搞混了!)
这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) ,MySQL 自带的引擎是MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。
并不是说只用一个日志模块不可以,只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么,又会有同学问,我用两个日志模块,但是不要这么复杂行不行,为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做?
- 先写 redo log 直接提交,然后写 binlog,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 bingog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。
- 先写 binlog,然后写 redo log,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。
如果采用 redo log 两阶段提交的方式就不一样了,写完 binglog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binglog 也已经写完了,这个时候发生了异常重启会怎么样呢? 这个就要依赖于 MySQL 的处理机制了,MySQL 的处理过程如下:
- 判断 redo log 是否完整,如果判断是完整的,就立即提交。
- 如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。
这样就解决了数据一致性的问题。