InnoDB 存储引擎是以页为单位来管理存储空间的,我们的增删改查本质上都是对页面上进行操作。我们知道在访问磁盘的时候,MySQL是会把数据加载到Buffer Pool然后进行操作的。对于DML操作,表、索引等的增删改DDL操作,还有数据本身是在Buffer Pool缓冲池中可能还没来得及刷新到磁盘中,系统或者服务器突然崩溃,那这些数据该怎么恢复呢?
上述问题就需要用到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本质上只是记录了一下事务对数据库做了哪些修改。InnoDB 针对事务对数据库的不同修改场景定义了多种类型的redo log,但是绝大部分类型的 redo log都有下面这种通用的结构:
redo log是以组的形式写入磁盘的。插入或者修改一条数据都会对B+树进行修改,可能不止有一个修改点,甚至会涉及到页分裂。为了保证这组数据的原子性,MySQL引入了mini-transaction(简称mtr)的概念。
mtr是当在 DML 操作期间在物理级别对内部数据结构进行更改时,InnoDB 处理的一个内部阶段。mtr没有回滚的概念。一个所谓的mini-Transaction 可以包含一组redo日志,在进行崩溃恢复时这一组redo 日志作为一个不可分割的整体。
一个事务可以包含若干条语句,每一条语句其实是由若干个mini-Transaction组成,每一个 mini-Transaction 又可以包含若干条 redo日志,最终形成了一个树形结构。
LSN是log sequence number(日志序列号)的缩写,用于记录日志序号,它是一个不断递增的整数。LSN的初始值是8704,也就是说LSN从8704开始递增。每一组由Mini-Transaction生成的redo log都有一个唯一的LSN值与其对应,LSN值越小,说明redo log产生的越早。通过LSN,可以具体地定位到其在redo log文件中的位置。
可以通过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
为了解决磁盘速度过慢的问题而引入了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 中。
前面说到log buffer中的数据会定时刷新到磁盘,这就涉及到redo log 刷盘时机了。可能触发刷盘的情况如下:
innodb_flush_log_at_trx_commit
作用于事务提交时,innodb_flush_log_at_trx_commit 变量可配置3个值:不同配置值的持久化程度
显然对性能的影响是随着持久化程度的增加而增加的。通常我们建议在日常场景将该值设置为1,但在系统高峰期临时修改成2以应对大负载。
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)。并且通过各种机制,避免无谓的页面修复,进而提升崩溃恢复的速度。
MySQL的数据目录下默认有两个名为ib_logfile0和ib_logfile1 的文件。可通过以下语句查看数据目录:
SHOW VARIABLES LIKE 'datadir';
redo log的目录和大小都是可修改的。
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 ❞