InnoDB 存储引擎是以页为单位来管理存储空间的,我们的增删改查本质上都是对页面上进行操作。我们知道在访问磁盘的时候,MySQL是会把数据加载到Buffer Pool然后进行操作的。对于DML操作,表、索引等的增删改DDL操作,还有数据本身是在Buffer Pool缓冲池中可能还没来得及刷新到磁盘中,系统或者服务器突然崩溃,那这些数据该怎么恢复呢?
redo log是什么
上述问题就需要用到redo log了,那redo log又是什么呢?
官网定义:
❝
A disk-based data structure used during crash recovery, to correct data written by incomplete transactions
❞
翻译过来就是,redo log是一种基于磁盘的数据结构,用于在故障恢复期间纠正由不完整事务写入的数据。
redo log让MySQL innodb引擎有崩溃自动恢复的能力。redo log是保证事务的完整性、持久性,只有innodb引擎支持事务,所以redo log也是innodb引擎独有的。
redo log 格式
redo log本质上只是记录了一下事务对数据库做了哪些修改。InnoDB 针对事务对数据库的不同修改场景定义了多种类型的redo log,但是绝大部分类型的 redo log都有下面这种通用的结构:
mini-transaction
redo log是以组的形式写入磁盘的。插入或者修改一条数据都会对B+树进行修改,可能不止有一个修改点,甚至会涉及到页分裂。为了保证这组数据的原子性,MySQL引入了mini-transaction(简称mtr)的概念。
mtr是当在 DML 操作期间在物理级别对内部数据结构进行更改时,InnoDB 处理的一个内部阶段。mtr没有回滚的概念。一个所谓的mini-Transaction 可以包含一组redo日志,在进行崩溃恢复时这一组redo 日志作为一个不可分割的整体。
一个事务可以包含若干条语句,每一条语句其实是由若干个mini-Transaction组成,每一个 mini-Transaction 又可以包含若干条 redo日志,最终形成了一个树形结构。
LSN
LSN是log sequence number(日志序列号)的缩写,用于记录日志序号,它是一个不断递增的整数。LSN的初始值是8704,也就是说LSN从8704开始递增。每一组由Mini-Transaction生成的redo log都有一个唯一的LSN值与其对应,LSN值越小,说明redo log产生的越早。通过LSN,可以具体地定位到其在redo log文件中的位置。
查看系统中的LSN值
可以通过SQL语句查看系统中LSN的值
SHOW ENGINE INNODB STATUS;
在结果中找到如下片段
---
LOG
---
Log sequence number 1765718166
Log flushed up to 1765718166
Pages flushed up to 1765718166
Last checkpoint at 1765718157
0 pending log flushes, 0 pending chkp writes
212 log i/o's done, 0.00 log i/o's/second
-
Log sequence number:系统中的LSN值,也就是当前系统已经写入的redo日志量,包括写入log buffer中的日志。 -
Log flushed up to:flushed_to_disk_lsn 的值,也就是当前系统已经写入磁盘的 redo 日志量。 -
Pages flushed up to:代表 flush链表中被最早修改的那个页面对应的 oldest_modification 属性值。 -
Last checkpoint at:当前系统的checkpoint_lsn值
Log buffer
为了解决磁盘速度过慢的问题而引入了Buffer Pool。同理,写入 redo log时也不能直接直接写到磁盘上,所以引入了Log buffer。Log buffer(日志缓冲区)是保存要写入磁盘上日志文件的数据的内存区域,Log buffer可通过变量innodb_log_buffer_size
配置,默认是16M。Log buffer的数据会定期的刷新到磁盘中,增加日志缓冲区大小可以支持大型事务,这样无需在事务提交之前将redo log写入磁盘,节省磁盘I/O,
InnoDB 为了更好地进行系统崩溃恢复,把通过Mini-Transaction 生成的redo日志都放在了大小为 512 字节的块(block)中,向log buffer中写入 redo log的过程是顺序的,也就是先写入到block中,当该 block的空闲空间用完之后再往下一个 block中写。Mini-Transaction运行过程中产生的一组 redo log,在Mini-Transaction结束时这组redo log会被复制到log buffer 中。
redo log 刷盘时机
前面说到log buffer中的数据会定时刷新到磁盘,这就涉及到redo log 刷盘时机了。可能触发刷盘的情况如下:
-
log buffer空间不足时:log buffer 的大小是有限的,如果不停地往这个有限大小的 log buffer里写入日志,很快它就会被填满。InnoDB认为如果当前写入log buffer 的 redo log量已经占满了log buffer 总容量的大约一半左右,就需要把这些日志刷新到磁盘上。
-
事务提交时:之所以使用redo log主要是因为它占用的空间少,还是顺序写,在事务提交时可以不把修改过的Buffer Pool 页面刷新到磁盘,但是为了保证持久性,必须要把修改这些页面对应的 redo log刷新到磁盘。
变量
innodb_flush_log_at_trx_commit
作用于事务提交时,innodb_flush_log_at_trx_commit 变量可配置3个值: -
当设置该值为1时,每次事务提交都要做一次刷盘,这是最安全的配置,即使宕机也不会丢失事务;也是默认值 -
当设置为2时,则在事务提交时只做写操作,只保证写到系统的page cache,因此实例崩溃不会丢失事务,但宕机则可能丢失事务; -
当设置为0时,事务提交不会触发redo写操作,而是留给后台线程每秒一次的刷盘操作,因此实例崩溃将最多丢失1秒钟内的事务。
显然对性能的影响是随着持久化程度的增加而增加的。通常我们建议在日常场景将该值设置为1,但在系统高峰期临时修改成2以应对大负载。
-
后台线程:默认每秒都会刷新一次log buffer中的redo log到磁盘。可以通过变量
innodb_flush_log_at_timeout
来控制后台线程的刷新频率 -
正常关闭服务器时等等
崩溃恢复
在服务器不挂的情况下,redo 日志简直就是个大累赘,不仅没用,反而让性能变得更差。在MySQL 8.0.21版本中,可以通过ALTER INSTANCE DISABLE INNODB REDO_LOG
语句来关闭redo Log,最好还是不要关闭,以防万一。万一数据库挂了,就可以在重启时根据redo日志中的记录将页面恢复到系统崩溃前的状态。
MySQL可以根据redo log中的各种LSN值,来确定恢复的起点和终点。然后将 redo log中的数据,以哈希表的形式将一个页面下的放到哈希表的一个槽中。之后就可以遍历哈希表,因为对同一个页面进行修改的 redo log都放在了一个槽里,所以可以一次性将一个页面修复好(避免了多次读取页面的随机I/O)。并且通过各种机制,避免无谓的页面修复,进而提升崩溃恢复的速度。
redo log 参数配置
MySQL的数据目录下默认有两个名为ib_logfile0和ib_logfile1 的文件。可通过以下语句查看数据目录:
SHOW VARIABLES LIKE 'datadir';
redo log的目录和大小都是可修改的。
-
innodb_log_group_home_dir:修改redo log生成目录,默认为当前数据目录所在文件夹下 -
innodb_log_file_size:单个redo log文件的大小,默认是48M -
innodb_log_files_in_group:redo log文件组的文件个数,默认是2,最大100
redo log在磁盘是以redo log文件组的形式存在的,这些文件的文件名以ib_logfile开头,以数字[N]结尾,N从0开始,N为正整数。
redo log文件是循环写入的,在写入文件时,先写入ib_logfile0,ib_logfile0写满了再写ib_logfile1,以此类推往下写,如果到最后一个文件也写满了,就从ib_logfile0重新开始写。
参考资料:
❝
http://mysql.taobao.org/monthly/2015/05/01/ https://dev.mysql.com/doc/refman/5.7/en/innodb-redo-log.html
❞
原文始发于微信公众号(索码理):【MySQL系列】- Redo log知多少
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/64162.html