前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
InnoDB 存储引擎是以页为单位来管理存储空间的,我们的增删改查本质上都是对页面上进行操作。我们知道在访问磁盘的时候,MySQL是会把数据加载到Buffer Pool然后进行操作的。对于DML操作,表、索引等的增删改DDL操作,还有数据本身是在Buffer Pool缓冲池中可能还没来得及刷新到磁盘中,系统或者服务器突然崩溃,那这些数据该怎么恢复呢?
官网定义:
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 日志本质上只是记录了一下事务对数据库做了哪些修改。 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(日志序列号)的缩写,用于记录日志序号,它是一个不断递增的 unsigned long 类型的整数。LSN的初始值是8704,也就是说LSN从8704开始递增。每一组由Mini-Transaction 生成的 redo log都有一个唯一的LSN值与
其对应,LSN 值越小,说明 redo 日志产生的越早。通过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 刷盘时机了。可能触发刷盘的情况如下:
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以应对大负载。
后台线程:默认每秒都会刷新一次log buffer中的redo log到磁盘。可以通过变量innodb_flush_log_at_timeout
来控制后台线程的刷新频率
正常关闭服务器时等等
在服务器不挂的情况下,redo 日志简直就是个大累赘,不仅没用,反而让性能变得更差。但是万一数据库挂了,就可以在重启时根据redo日志中的记录将页面恢复到系统崩溃前的状态。
MySQL可以根据redo log中的各种LSN值,来确定恢复的起点和终点。然后将 redo log中的数据,以哈希表的形式将一个页面下的放到哈希表的一个槽中。之后就可以遍历哈希表,因为对同一个页面进行修改的 redo log都放在了一个槽里,所以可以一次性将一个页面修复好(避免了很多读取页面的随机 IO)。并且通过各种机制,避免无谓的页面修复,比如已经刷新的页面,进而提升崩溃恢复的速度。
在MySQL 8.0.21版本中,可以通过ALTER INSTANCE DISABLE INNODB REDO_LOG
语句来关闭redo Log,但是最好还是不要关闭。
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