任何项目都会有日志,MySQL也不例外,而且MySQL更是其中的佼佼者,日志种类繁多,而本篇的目的就是全解MySQL中的各类日志,如撤销日志、错误日志、慢查询日志、中继日志、回滚日志.....
其实日志的作用不言而喻,无论是线上排查,亦或是性能优化,几乎都需要从日志中来获得信息作为依据,而MySQL中,很多很多的功能也都需要基于日志实现,比如事务回滚、数据持久化、数据恢复、数据迁移、MVCC机制.....,因此本篇去阐述日志,也是为了方便撰写后续的其他文章。
OK,废话不多说了,咱们现在就开始吧~
一、Undo-log撤销日志
Undo即撤销的意思,但咱们通常也习惯称它为回滚日志,在日常开发过程中,如果代码敲错了,一般会习惯性的按下Ctrl+Z撤销,而Undo-log的作用也是如此,但它是用来给MySQL撤销SQL操作的。
在之前的《SQL执行篇》中曾聊到过,当一条写入类型的SQL执行时,都会记录Undo-log日志,会生成相应的反SQL放入到Undo-log中,例如:
- 如果目前是
insert插入操作,则生成一个对应的delete操作。 - 如果目前是
delete删除操作,InnoDB中会修改隐藏字段deleted_bit=1,则生成改为0的语句。 - 如果目前的
update修改操作,比如将姓名从竹子改成了熊猫,那就生成一个从熊猫改回竹子的操作。
当事务中某条SQL执行失败时,MySQL就需要回滚事务中其他执行成功的SQL,此时就会找到这个事务在Undo-log中生成的反SQL,然后将库中的数据改回事务发生前的样子。
大家看这段话,似乎没有啥问题对不?也包括我之前的文章中也是这样去描述的,但这里其实存在些许误导,在之前的《SQL执行篇》中,我们说一条写
SQL执行前,会生成对应的反SQL记录在Undo-log中,但实际上并不会生成反SQL,这样去叙述仅是为了方便大家理解罢了。
那咱们怎么证明不会生成反SQL呢?如果大家有仔细研究过MySQL的日志,应该会发现Undo-log并不存在单独的日志文件,也就是磁盘中并不会存在xx-undo.log这类的文件,那Undo-log存在哪儿呢?InnoDB默认是将Undo-log存储在xx.ibdata共享表数据文件当中,默认采用段的形式存储。
也就是当一个事务尝试写某行表数据时,首先会将旧数据拷贝到xx.ibdata文件中,将表中行数据的隐藏字段:roll_ptr回滚指针会指向xx.ibdata文件中的旧数据,然后再写表上的数据。
那
Undo-log究竟在xx.ibdata文件中怎么存储呢?在共享表数据文件中,有一块区域名为Rollback Segment回滚段,每个回滚段中有1024个Undo-log Segment,每个Undo段可存储一条旧数据,而执行写SQL时,Undo-log就是写入到这些段中。
不过在MySQL5.5版本前,默认只有一个Rollback Segment,而在MySQL5.5版本后,默认有128个回滚段,即支持128*1024条Undo记录同时存在。
1.1、对于事务回滚原理的纠正
结合上述纠正后的内容,咱们再对事务的回滚原理稍作更正,实际上当一个事务需要回滚时,本质上并不会以执行反SQL的模式还原数据,而是直接将roll_ptr回滚指针指向的Undo记录,从xx.ibdata共享表数据文件中拷贝到xx.ibd表数据文件,覆盖掉原本改动过的数据。
还是上个图简单理解一下吧,如下:

一条写SQL执行的流程如上图中的序号所示,当需要回滚事务时,直接用Undo旧记录覆盖表中修改过的新记录即可!
如果是
insert操作,由于插入之前这条数据都不存在,那么就不会产生Undo记录,此时回滚时如何删除这条记录呢?因为插入操作不会产生Undo旧记录,因此隐藏字段中的roll_ptr=null,因此直接用null覆盖插入的新记录即可,这样也就实现了删除数据的效果~
1.2、基于Undo版本链实现MVCC
认真阅读过《MySQL-MVCC机制原理剖析》的小伙伴对于这点并不陌生,Undo-log中记录的旧数据并不仅仅只有一条,一条相同的行数据可能存在多条不同版本的Undo记录,内部会通过roll_ptr回滚指针,组成一个单向链表,而这个链表则被称之为Undo版本链,案例如下:
-- 事务T1:trx_id=1(两次修改同一条数据)
UPDATE `zz_users` SET user_name = "竹子" WHERE user_id = 1;
UPDATE `zz_users` SET user_sex = "男" WHERE user_id = 1;
复制代码
Undo-log中的旧数据版本链示意图大致如下:

当然,对于如何利用版本链实现MVCC机制,这点就不反复赘述了,没了解过的可以去看关于MVCC原理剖析的那篇文章。
1.3、Undo-log的内存缓冲区
InnoDB在MySQL启动时,会在内存中构建一个BufferPool,而这个缓冲池主要存放两类东西,一类是数据相关的缓冲,如索引、锁、表数据等,另一类则是各种日志的缓冲,如Undo、Bin、Redo....等日志。
而当一条写SQL执行时,不会直接去往磁盘中的xx.ibdata文件写数据,而是会写在undo_log_buffer缓冲区中,因为工作线程直接去写磁盘太影响效率了,写进缓冲区后会由后台线程去刷写磁盘。
OK~,那么如果当一个事务提交时,
Undo的旧记录会不会立马被删除呢?因为事务都提交了,不需要再回滚改动过的数据,似乎用不上Undo旧记录了,对吗?确实如此,但不会立马删除Undo记录,对于旧记录的删除工作,InnoDB中会有专门的purger线程负责,purger线程内部会维护一个ReadView,它会以此作为判断依据,来决定何时移除Undo记录。
为什么不是事务提交后立马删除Undo记录呢?因为可能会有其他事务在通过快照,读Undo版本链中的旧数据,直接移除可能会导致其他事务读不到数据,因此删除的工作就交给了purger线程。
1.4、Undo-log相关的参数
最后再来看看关于Undo-log的一些参数,其实在MySQL5.5之前没有太多参数,如下:
innodb_max_undo_log_size:本地磁盘文件中,Undo-log的最大值,默认1GB。innodb_rollback_segments:指定回滚段的数量,默认为1个。
除开上述两个参数外,其他参数基本上是在MySQL5.6才有的,如下:
innodb_undo_directory:指定Undo-log的存放目录,默认放在.ibdata文件中。innodb_undo_logs:指定回滚段的数量,默认为128个,也就是之前的innodb_rollback_segments。innodb_undo_

最低0.47元/天 解锁文章
1093

被折叠的 条评论
为什么被折叠?



