# Mysql学习(四)——InnoDB中的日志处理(Undo、Redo、Binlog)
[TOC]
我们使用的InnoDB和Mysql常用的是三种日志,分别为Undo log、Redo log、Binary log,这三种日志分别对应着:撤销日志、重做日志、二进制日志
## Undo log 日志原理
**Undo日志介绍**:
- Undo日志意味撤销日志,当数据库事务开始时,将需要修改的原始记录先保存一份到Undo日志当中,这样的话当事务发生回滚或者数据库崩溃时,可以从Undo日志中撤销未提交的事务对数据库的影响
**Undo日志的产生与销毁**:
- Undo log会在事务开始前产生,在事务提交时,并不会立刻删除Undo log,innoDB会将该事务对应的Undo log放入删除列表中,后面会通过后台线程Purge thead进行回收,Undo log是一个逻辑日志,记录一个变化的过程。比如如果是一个insert语句,那么Undo日志中就存放一个delete语句,如果执行一个delete语句,那么Undo日志就存放一个insert语句,如果是一个update语句,那么就存放一个恢复原始数据的update语句
**Undo Log存储**:
undo log采用段的方式管理和记录。在innodb数据文件中包含一种rollback segment回滚段,内部包含1024个undo log segment。可以通过下面一组参数来控制Undo log存储。
```sql
show variables like '%innodb_undo%';
```

### Undo Log的作用与工作原理
1、保障事务的原子性,事务处理过程中,如果出现了错误或者用户执行了 ROLLBACK 语句,MySQL 可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。
2、事务并发多版本控制(MVCC)
主要是事务未提交时,Undo log里存放了未提交事务时的原始数据,那么此时其他线程就来Undo log中读取旧版本数据,这样保障了并发情况下,事务未提交也不影响其他线程读取数据,这种也称为**快照读**

事务A首先进行update age = 20,此时将原始数据写入到了Undo Buffer中,并且未提交事务
事务B这时候来进行查询,直接读取的Undo log中的数据,进行快照读,避免了读未提交的场景。
## Redo log
其实Redo log和Bin log日志有点相似,也是mysql日志系统中非常重要的日志组件,先介绍下Redo log
**Redo log 介绍**:
- Redo log意味着重做日志,主要就是用于恢复数据库异常时的日志
- Redo log存有所有在事务中修改的数据记录,并将其最新的数据备份在Redo Log中
**Redo log的生成与释放**:
- 随着事务操作的执行,就会产生redo log,在事务提交时会将产生的Redo log写入到Log Buffer中,而此时redo log 也不是事务提交就马上进行刷盘。等到事务操作的脏页被刷盘后,redo log就完成了使命,可以被重用了,就会被清除并留作后面覆盖写入
**Redo log的写入机制**:
- redo log的写入机制与bin log有点不同,是写满文件之后,又从头开始覆盖写

write pos意味着写入的指针,而Check point 意味着擦除的指针,当写满之后,write pos又会回到第一个日志文件,继续开始写,而check point 也在向后进行循环擦除,当write pos赶上check point日志指针时,需要等待check point擦除记录后再写入
### Redo log的工作原理
Redo log为了实现事务持久化而出现的,主要是为了防止当mysql出现故障时,还未有脏页数据写入到IBD文件中,在重启mysql时,会进行崩溃恢复(crash-safe),从redo日志中进行重做,达到事务数据未被刷盘也能持久化的目的
<img src="https://zhengyao.space/upload/2020/12/image-20201210231743013-4d8ea0efbdc74e58a3316f5a9dbb6c67.png" alt="image-20201210231743013" style="zoom: 67%;" />
当用户进行修改数据时,首先先将原始数据存入到Undo log中,判断数据是否在BP中,如果在,则进行修改,否则进行查询到BP中后进行修改,将修改后的记录写入到Redo Log中,用于恢复新数据或者回滚原有数据,数据在BP中等待page cleaner thread 将脏页数据进行刷盘。
**Redo log刷盘配置**
Redo log的刷盘和mysql脏页刷盘有着类似的机制,即通过参数进行控制
```sql
show valiables like '%innodb_logs%'
```
Redo Buffer持久化到Redo log中可以通过参数`innodb_flush_log_at_trx_commit`进行设置:
- 设置为0的话,每隔1秒进行提交OS CACHE并刷盘,如果mysql服务挂了可能会导致丢失一秒的数据
- 设置为1,每次事务提交都提交os cache 并刷盘,最安全也是性能最低的方式
- 设置为2,每次都事务提交都提交到os cache中,每隔1秒由os cache进行刷盘,这样的话mysql 服务挂了,服务器没挂就不会丢失这1秒的数据

## Bin log日志
上面的Redo log 是InnoDB引擎所特有的日志,而Bin log就是mysql自带的日志组件了,bin log主要使用二进制记录了所有对数据库数据进行修改的操作,其中Bin log是使用log event的方式记录日志的,不仅记录了日志的执行记录,也记录了日志的执行时间,执行id等,开启Bin log大概会损失1%的性能,主要是为了以下两种场景:
- 主从复制:在主库中开启Bin log日志,这样可以将日志传递给从库,从库使用Bin log进行数据恢复,达到主从数据一致的目的
- 数据恢复:使用mysqlbinlog工具等恢复数据
**Binlog文件记录方式**:Bin log 主要有三种文件记录模式,分别为Row,Statement,Mixed三种
- Row:日志会记录每一行修改数据sql的详情,然后可以在slave端进行相同的修改
- 优点:记录数据修改的每一个细节,能够保证数据一致性,实现完全的主从同步
- 缺点:日志记录数会很多,特别是执行alter table 语句的时候,会对表里每一行数据都进行一个日志记录
- Statement:每一条被修改数据的SQL都会记录到master的Binlog中,slave在复制的时候SQL进程会解析成和原来master端执行过的相同的SQL再次执行。简称SQL语句复制
- 优点:可以减少日志量,减少磁盘的IO,减少占用空间,提升性能
- 缺点:如果碰到一些变量,例如last_insert_id(),limit函数等,就会导致主从数据不一致,但是例如now()函数,innodb会在binlog中将时间节点设置为当时更新的时间,这样无论何时更新数据,now()函数也是当时时间节点的设置,避免了主从延迟时间节点不一致导致数据不一致的问题。
- Mixed:混合式,一般会使用Statment这种方式执行数据,mysql会进行判断,对于Statement模式无法复制的sql,就会切换到Row这种记录方式,保障主从数据一致性。
### Bin log写入机制
1、根据记录模式和操作触发event事件后生成log event(事件触发执行机制)
2、将事务执行过程中产生的log event写入缓冲区,每个事务线程都会有个缓冲区,log Event保存在`binlog_cache_mngr`的数据结构中,数据结构分为两种缓冲区,一个是用于不支持事务的`stmt_cache`,一个是支持事务的`trx_cache`
3、事务在提交阶段会将产生的log event写入到外部的bin log文件中,并且不同的事务是使用串行的方式写入的,保证一个事务写入的日志是连续的,中间不会插入其他事务
### Bin log常用配置
1. 查看Bin log 是否开启`show variables like 'log_bin';`

如果bin log未开启的话,是不能直接使用`set global log_bin=on`来开启的,需要修改my.cnf或者my.ini并重启后才能生效
```ini
#log-bin=ON
#log-bin-basename=mysqlbinlog
binlog-format=ROW #日志模式
log-bin=mysqlbinlog #开启binlog,并且日志文件名为mysqlbinlog
```
2. 使用show binlog events命令
```sql
show binary logs; #等价于show master logs;
show master status;
show binlog events;
show binlog events in 'mysqlbinlog.000001';
```
3. 使用mysqlbinlog 命令
```sql
mysqlbinlog "文件名" #查看对应的binlog 文件
mysqlbinlog "文件名" > "test.sql" #将对应binlog文件写入到test.sql中,这样可以下载后查看
```
4. 使用 binlog 恢复数据
```sql
#按指定时间恢复
mysqlbinlog --start-datetime="2020-04-25 18:00:00" --stop-
datetime="2020-04-26 00:00:00" mysqlbinlog.000002 | mysql -uroot -p1234
#按事件位置号恢复
mysqlbinlog --start-position=154 --stop-position=957 mysqlbinlog.000002
| mysql -uroot -p1234
```
mysqldump:定期全部备份数据库数据。mysqlbinlog可以做增量备份和恢复操作。
5. 删除Binlog文件
```sql
purge binary logs to 'mysqlbinlog.000001'; #删除指定文件
purge binary logs before '2020-04-28 00:00:00'; #删除指定时间之前的文件
reset master; #清除所有文件
```
6. 设置bin log的归档天数
```sql
show variables like '%expire_logs_days%';
```

这样就是保存7天内的归档日志
## Bin log 和 Redo log 日志的不同点
Binlog 和Redo log的区别主要有下列四点:
- Bin log是属于Mysql的组件,Redo log 是innoDB所特有的组件
- Bin log 属于逻辑记录日志,记录的是语句的原始逻辑,比如给C表这条数据age+1,Redo log 属于物理记录日志,记录的是我这条数据发生了什么改变
- Bin log 写入数据是写满一个日志文件就会继续新建一个文件继续写,redo log的日志大小是固定的,写满了之后就从头开始写
- Redo log可以在宕机之后自动从日志中进行恢复,具有crash-safe能力,而bin log需要手工使用工具进行恢复
## Redo log的二段提交机制
其实Redo log与Bin log既然都是为了保存DML类型的语句而产生的日志,并且都具有恢复数据的能力,那么如何保障Redo log 和Bin log两者恢复数据的一致性呢。
这里就要讲上面的Update语句的流程了,其实Update语句比上面说的过程还会更复杂,例如执行一条语句假设age = 1,执行`update c set age = age+1 where id = 2`;
1、sql的执行器先找引擎取ID=2这一行,如果ID=2这一行所在的数据页就在内存中,那么会直接去修改内存,如果不在内存中,会去磁盘中做一次读取后在进行修改内存
2、执行器拿到引擎给的行数据,随后将这个值+1,得到新的一条数据后,调用引擎写入新数据
3、引擎得到这条新数据后,会先将这条数据更新到内存中,即为脏页,并且此时将更新操作记录到Redo log中,此时Redo log处于 Prepare状态,然后告知执行器执行成功了,随时可以提交事务
4、执行器生成这个更新操作的bin log,并且将bin log写入磁盘中
5、执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成 提交(commit)状态,更新完成
**那么问题来了,为什么需要先写Redo log并且只是置为Prepare状态,后写bin log,再将Redo log置为提交态呢?**
假设我们先写Redo Log,后写Bin log,不需要二段提交机制的话:
- 例如上面这条sql,如果执行到写完了Redo Log时,Mysql崩溃了,导致没有写Bin log,那么在崩溃恢复的时候,就会导致bin log恢复出来的数据age=1,而根据Redo log恢复出来的age=2,导致数据不匹配,数据库丢失了这一次更新
假设我们先写Bin log,再写Redo log呢?
- 在写完Bin log后,再写Redo log时,由于redo log还没写,崩溃恢复时这个事务其实是不存在的,那么bin log恢复出来的数据就是2,而其实这个时候数据还未更新,导致数据库多出了这一次更新。
所以只有采用2段提交机制,才能够保障Bin log 和Redo log两边恢复数据的一致性。
其实这里就是为了保障数据的原子性,要么都执行成功,要么都执行失败
## Redo log 崩溃恢复的机制
前面也介绍了,Redo log有两阶段提交机制,一个是Prepare阶段,一个是Commit阶段,而Redo log和有一个共同的标识字段称为XID
当Mysql进行崩溃恢复时,去扫描Redo log:
- 如果发现这个记录既有Prepare 又有Commit,则证明该数据已修改,直接提交
- 如果发现这个记录只有Prepare时,则需要使用Xid去Bin log中进行查找
- 如果bin log没有查找到记录,则证明还未执行完成,直接回滚
- 如果bin log查找到这条记录,则可以说此条sql以执行完成,两边使用日志恢复出来的数据一致,那么可以直接提交
Mysql学习(四)——InnoDB中的日志处理(Undo、Redo、Binlog)