undolog日志叫做回滚日志,它可以用于回滚事务和实现MVCC。
1)回滚事务->保障事务的原子性
在执行事务的过程中,将回滚所需的信息都记录在undolog中,回滚时根据undolog中的数据做相反的操作。
比如插入记录时,将这条记录的主键值记录下来,回滚时就删除这个主键值的记录。
删除记录时,将这条记录的内容记录下来,回滚时就插入这些内容。
更新记录时,将这条记录被更新的列的旧值记录下来,回滚时就更新该列为旧值。
<版本链>
一条记录每次被修改时产生的undolog格式都有一个roll_pointer指针和trx_id事务id:
2)MVCC
配合ReadView,undolog还可以用于实现MVCC(多版本并发控制)。
可以通过以下内容进行深入了解(事务的隔离级别是如何实现的?)
Mysql事务隔离级别是如何实现的?https://blog.csdn.net/weixin_51299539/article/details/130535017?spm=1001.2014.3001.5501
Mysql数据库的数据是存在磁盘中的,如果每次都从磁盘获取数据,会大大降低数据库的读写性能,所以,innodb存储引擎设计了一个缓冲池Buffer Pool。
缓冲池被划分为许多页,叫做缓存页,除了包含索引页和数据页,还包含了插入缓存页、undo页(undo log写入这里)、自适应哈希索引和锁信息。
当我们需要缓存某条记录时,会将该记录所在页载入到缓冲区中。
读取数据时,先去这个缓冲池看看能不能找到,找不到才去磁盘找。
修改数据时,如果这个数据在缓冲池,会直接修改数据所在的页,并将其设置为脏页(也就是缓冲池和磁盘中的数据已经不一致了),为了减少磁盘I/O,不会立即将脏页写入磁盘,而是由一个后台线程选择一个合适的时机将脏页写入磁盘。
我们知道,undo log记录的是事务开始前的数据状态,也就是更新之前的值。而redo log,则是记录事务完成后也就是提交之后的数据状态,也就是更新之后的值。
它是一个物理日志,记录的是对哪个表空间的哪个数据页的什么偏移量的地方做了什么修改。
为了防止内存中的缓冲池数据丢失,每次修改缓冲池中的数据页后,mysql都会将本页的修改记录在redo log中,后续再由专门的后台线程负责将缓存在缓冲池中的脏页刷新到磁盘中。这就是WAL技术(Witre-Ahead Logging)。
WAL技术指的是,Mysql的写操作不是直接向数写到磁盘,而是先以日志的形式记录下修改的内容,然后在合适的时间再写回磁盘。
具体流程:
这样,更新也就完成了。
为什么说更新就算完成了呢?
redo log是记录的事务提交之后的数据修改,如果脏页中的修改记录还没有被写回磁盘,mysql就发生异常崩溃了,这个时候虽然缓冲池中的脏页数据丢失了,但是redo log中还保存了相应的数据,mysql重启后,可以根据redo log中的内容将数据库恢复到最新状态。
Tip:数据更新后,缓冲池中的undo页面也会被修改,也同样会记录到redo log中。
因此,通过redo log和WAL技术,mysql拥有crash-safe的能力,也就是保证了事务的持久性。
以redo日志形式将数据写入磁盘,有什么好处?
写入redo log采用的方式是追加操作,所以磁盘操作是“顺序写”。相比将数据直接写入磁盘的操作,需要先找到相应的位置,再执行写操作,这样是“随机写”,写入磁盘的开销大。
redo log是直接写入磁盘吗?->不是的!
每生成一条redo log,都会先被写入redo log buffer中,后面再持久化到磁盘。
那么redo log buffer中的数据何时刷新到磁盘?
->通过innodb_flush_log_at_trx_commit这个参数控制。
默认情况下,该参数值为1,也就是事务开始后,执行的更新操作记录在redo log buffer,事务提交后会直接刷新到磁盘。
参数值为0时,事务开始后执行的更新操作记录在redo log buffer,事务提交后不会主动触发写入磁盘的操作。每隔1s,redo log buffer中的数据会由一个后台线程调用write()写到操作系统的page cache中,接着再调用fsync()写入到磁盘。这种情况下,mysql进程的崩溃会导致上一秒的所有事务数据丢失。
参数值为2时,事务开始后执行的更新操作记录在redo log buffer,事务提交后,由后台线程调用write()将redo log buffer中的数据写入到操作系统的page cache中,后台线程会每隔1s调用fysnc()将page cache中数据写入磁盘。这种情况下,mysql进程的崩溃不会丢失数据,因为数据已经保存在操作系统的内核空间中,只有操作系统崩溃后,才会丢失数据。
<三种参数对应的写入性能与安全性>
写入性能:0 > 2 > 1
安全性: 1 > 2 > 0
可以发现,安全性越好,写入性能越差。写入性能越好,安全性越差。
InnoDB存储引擎中有一个重做日志文件组,它包含两个redo log文件,一个是ib_logfile0,一个是ib_logfile1,redo的写入方式采用的是循环写。从头开始写,写到末尾又会重头开始写。
其中,会有一个write_pos和check_point。
他们会按照顺时针方向运动,write_pos沿顺时针方向一直到check_point的部分表示可以写入的空间。write_pos沿顺时针方向一直到check_point的部分表示待落盘的脏数据页记录(本质上,redo就是保存的未被刷新的脏页数据)。当write_pos追上check_point时,redo log写满,mysql会被阻塞,此时不能再进行写操作,这时会停下来将buffer pool中的脏页刷新到磁盘,接着,既然脏页中的记录已经落盘,就可以擦除redo中的一些记录,check_point也向后顺时针移动,这样,write_pos与check_point之间就有了可以写入的空间。
undolog 和 redolog都是由InnoDB存储引擎生成的,也是InnoDB所特有的用于配合其支持事务的特性。
mysql在进行更新操作后,server层还会生成一条binlog,事务提交后,事务执行过程中产生的所有binlog都会统一写到binlog文件中。
1)适用对象不同
binlog是mysql的Server层实现的日志,所有存储引擎都可以使用
redo log是InnoDB存储引擎实现的日志,其它存储引擎不可以用
2)文件格式不同
binlog有三种格式类型:
a.Statement(逻辑日志):记录的是每一条修改数据的sql。缺点就是sql中使用的同一个动态函数,可能在主从数据库中得到的结果并不一致。(比如now(),uuid()等)。
b.Row(非物理日志):记录的是每一行记录的变化过程。缺点是记录了每一行数据的变更细节,导致binlog文件过大(比如批量的update操作,在statement中只记录一条sql,但是row中会产生每一次修改的变更情况)。
c.Mixed:根据情况选择上面两种类型中的一种。
redo log是物理日志,记录的是在某个数据页做了什么修改。比如对哪个表空间的哪个数据页的多少偏移量的地方进行了什么更新操作。
3)写入方式不同
redo log是循环写,日志大小固定,保存未被刷入磁盘的的脏页日志。
binlog是追加写,写满一个文件就创建一个新的文件继续写,保存的是全量的日志。
4)用途不同
redo log用于掉电等故障恢复。(有crash-safe能力)
binlog用于备份恢复、主从复制。(无crash-safe能力)
为什么binlog没有crash-safe能力?
因为事务提交后,虽然更新操作都在binlog中,但是它没有一个标志判断哪些是已经刷盘,哪些还未刷盘,那么就无法达到crash-safe的能力
但是redo log不一样,它记录的就是未刷盘的数据,只要mysql重启后,直接将这些数据恢复至内存即可。
<主从复制的原理>
主库在每次事务提交的时候会将执行的更新操作记录在binlog日志中,
接着,从库的IO线程会通过连接主库的log dump线程,读取主库的binlog日志并将读取到的数据写入到从库的relay log中继日志中,
最后,从库的SQL线程会读取并重新执行中继日志中的事件。
从库是不是越多越好?->不是的!
从库数量增多,IO线程也会增多(每个从库对应一个IO线程),那么主库的log dump线程也会增多,会大大消耗主库的资源,也会受限于主库的网络带宽。一般都是2-3个从库为宜。
<主从复制模型>(疑问)
binlog什么时候刷盘?
事务执行过程中,日志信息会先被写入到binlog cache,事务提交的时候,binlog cache中的数据会被写入到操作系统内核的page cache中(write())。
sync_binlog参数的值决定了什么时候page cache中的binlog刷新到磁盘的binlog文件中。
sync_binlog值为0(默认值)时,刷盘操作fsync()由操作系统自行决定
sync_binlog值为1时,binlog在写入到page cache后马上被刷新到磁盘
sync_binlog值N(N>1)时,表示page cache中的binlog在积累了N个事务的操作后才刷新到磁盘
下面,以具体执行一条更新操作:UPDATE t_user SET name = 'xiaolin' WHERE id = 1;为例,分析mysql更新操作的执行流程:
为什么要两阶段提交?
redo log刷盘与binlog刷盘是两个独立的逻辑,两个操作如果不能同时成功,会导致主从数据库不一致。
如果在redo log刷盘后,mysql突然宕机,binlog还没来得及写入,这样,mysql重启后,主库通过redolog可以得到最新的数据库数据,但是从库通过binlog复制的数据库与主库则会出现不一致。
如果在binlog刷盘后,mysql突然宕机,redo log还没来得及写入,这样,mysql重启后,主库通过redolog无法恢复事务的执行操作,得到的是旧数据。但是从库通过binlog复制的数据库却是最新的,与主库出现不一致。
<两阶段提交的过程>
当客户端执行commit或者自动提交事务时,Mysql会开启一个内部事务XA,并且会分两阶段完成对XA事务的提交:
具体就是将redo log的操作拆成了两个阶段:prepare阶段和commit阶段,中间穿插对bin log的写入。
在prepare阶段:会将XA事务的id(XID)写入redo log,并将redo log的事务状态设置为prepare,然后将redo log持久化到磁盘(innodb_flush_log_at_trx_commit = 1 )。
在commit阶段:把XID写入bin log,并将binlog持久化到磁盘(sync_binlog = 1),接着调用引擎的提交事务接口将redo log事务状态设置为commit。(bin log写入成功为事务提交成功的标志)
两阶段提交是如何保证主从数据库的一致的?
我们假设mysql分别在下面的A、B时刻发生异常重启:
mysql重启后扫描redo log,发现处于prepare状态的redo log,就拿着redo log中的XID去binlog中查找是否有这个XID:
1)发现binlog中没有XID(A时刻),说明redo log完成刷盘,但是binlog还未完成刷盘,则回滚事务。
2)发现binlog中有XID(B时刻),说明redo log和binlog都已经刷盘,则提交事务。
因此,在mysql异常重启后,binlog已经刷盘,说明redo log也已经刷盘,这样一来,重启后主库提交事务,从库如果用binlog复制主库数据,可以保证主从数据库数据一致。
两阶段提交有什么问题?
1)磁盘IO频繁
一般为了避免日志的丢失,我们都会设置控制redo log和binlog何时刷盘的参数为1,可是这样一来,每次事务提交的时候,触发XA事务,而这期间,会触发两次磁盘IO操作。
2)锁竞争激烈
多事务情况下,需要通过prepare_commit_mutex锁来保证事务提交的顺序,也就是,只有一个事务到commit阶段结束后才释放锁,另一个事务才可以进入prepare阶段。
如何解决两阶段提交的两个问题? -> binlog组提交
当有多个事务提交时,将多个事务的binlog刷盘操作合并成一个,从而减少磁盘IO。
prepare阶段不变,commit阶段被分成三个过程:
上面每个阶段都对应一个队列,每个阶段都有锁保证事务的写入顺序,这样一来,不再是锁住整个commit阶段,锁的粒度减小了,使得多个阶段可以并发执行,从而提升效率。
有binlog组提交,那么有redo log组提交吗?
在mysql5.6没有redo log组提交,mysql5.7才有redo log组提交
在mysql 5.7中,在prepare阶段不再让事务各自执行redo log刷盘操作,而是将其推迟到了commit阶段的flush阶段,从而实现了对redo log的一次组写入。
最终版--分析mysql更新操作的执行流程:
以具体执行一条更新操作:UPDATE t_user SET name = 'xiaolin' WHERE id = 1;为例:
prepare阶段 -> 将redo log状态设置为prepare,并将redo log刷盘
commit阶段 -> 将binlog刷盘,并调用存储引擎的提交事务接口,将redo log状态设置为commit(将事务设置为commit状态后,刷入到磁盘redo log文件)。
12.更新语句执行结束。