MySQL中redo日志

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

MySQL中redo⽇志
重做⽇志⽤来实现事务的持久性,即ACID中的D,由两部分组成:
⼀是内存中的重做⽇志缓冲(redo log buffer) 易丢失
⼆是重做⽇志⽂件(redo log file) 持久的
InnoDB是事务的存储引擎,其通过Force Log at Commit 机制实现事务的持久性,即当事务提交commit时,必须先将事务的所有⽇志写⼊到重做⽇志⽂件进⾏持久化,待事务COMMIT操作完成才算完成,这⾥的⽇志指重做⽇志,在InnoDB存储引擎中,由两部分组成,即redo log 和undo Log. redo log⽤来保证事务的持久性,undo log ⽤来帮主事务回滚及MVCC的功能。

redo log 基本是顺序写的,在数据库运⾏时不需要对redo log的⽂件进⾏读取操作。

⽽undo log 是需要进⾏随机读写的
为了确保每次⽇志都能写⼊⽇志⽂件,在每次将重做⽇志缓冲写⼊重做⽇志⽂件后,InnoDB存储引擎都需要调⽤⼀次fsync操作,由于重做⽇志⽂件打开并没有使⽤O_DIRECT选项,因此重做⽇志缓冲先写⼊⽂件系统缓存。

为了确保重做⽇志写⼊磁盘,必须进⾏fsync操作。

由于fsync的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能
InnoDB存储引擎允许⽤户⼿⼯设置⾮持久性的情况发⽣,以此来提⾼数据库的性能。

即当事务提交时,⽇志不写⼊重做⽇志,⽽是等待⼀个时间周期后在执⾏fsync操作。

由于并⾮强制在事务提交时进⾏⼀次fsync操作,显然这可以提⾼数据库的性能,但是当数据库发⽣宕机时,由于部分⽇志未书安⼼到磁盘,因此会丢失最后⼀段的事务
参数innodb_flush_log_at_trx_commit⽤来控制重做⽇志刷新到磁盘的策略,改参数默认为1 ,表⽰事务提交时必须调⽤⼀次fsync操作,还可以设置成0 和2 ,0表⽰事务提交时不进⾏写⼊重做⽇志操作,这个操作仅在master thread中完成,⽽master thread中每⼀秒会进⾏⼀次重做⽇志⽂件的fsync操作,2 表⽰事务提交时将重做⽇志写⼊重做⽇志⽂件,但仅写⼊⽂件系统的缓存中,不进⾏fsync操作。

在这个设置下,当MySQL发⽣宕机⽽操作系统不发⽣宕机时,并不会导致事务的丢失,⽽当操作系统宕机时,重启数据库会丢失未从⽂件系统缓存刷新到重做⽇志⽂件的那部分事务
参考⼀个列⼦,⽐较innodb_flush_log_at_trx_commit对事务的影响。

⾸先根据如下代码创建表t1和存储过程p_load
CREATE TABLE test_load(
a INT,
b CHAR(80)
)ENGINE=INNODB;
DELIMITER //
CREATE PROCEDURE p_load(COUNT INT UNSIGNED)
BEGIN
DECLARE s INT UNSIGNED DEFAULT1;
DECLARE c CHAR(80) DEFAULT REPEAT('a',80);
WHILE s<=COUNT DO
INSERT INTO test_load SELECT NULL,c;
COMMIT;
SET s = s+1;
END WHILE;
END;
//
DELIMITER ;
存储过程p_load的作⽤是将数据不断的插⼊表test_load中,并且每插⼊⼀条就进⾏⼀次显⽰的COMMIT操作,在默认的设置下,参数innodb_flush_log_at_trx_commit为1的情况下,InnoDB会将重做⽇志缓冲写⼊⽂件,并且调⽤⼀次fsync操作,如果执⾏CALL p_load(500 000),则会向表中插⼊50W⾏的记录,执⾏50W次的fsync操作,先看看默认情况下插⼊50W条记录需要的时间
CALL p_load(500000);
虚拟机,插⼊50W条记录,开销18分钟,对于⽣产环境来说,时间肯定是不能接受的,⽽造成时间⽐较馋的原因是在于fsync所需要的时间,那么将参数设置成
SET GLOBAL innodb_flush_log_at_trx_commit=0;
再执⾏
CALL p_load(500000); 总耗时40秒
可以看到参数innodb_flush_log_at_trx_commit设置为0后,插⼊50W⾏的记录缩短了接近17分钟。

造成这么⼤现象的主要原因是后者⼤⼤减少了fsync的次数,从⽽挺⾼了数据库执⾏的性能,如下表显⽰了innodb_flush_log_at_trx_commit的不同设置下,调⽤存储过程p_load插⼊50W⾏记录的时间
虽然⽤户可以设置参数innodb_flush_log_at_trx_commit为0或2来提⾼事务提交的性能,但是需要牢记,这种设置丧失了事务的ACID特性,⽽针对上述存储过程,为了提⾼事务的提交性能,应该在将50W⾏记录插⼊表后进⾏⼀次的COMMIT操作,⽽不是没插⼊⼀条记录后进⾏⼀次COMMIT操作。

这样的好处是还可以使事务⽅法在回滚时会滚到事务最开始的确定状态
在MySQL数据库中还有⼀种⼆进制⽇志其⽤来进⾏POINT-IN-TIME(PIT)的恢复及主从复制(Replication)环境的建⽴,从表⾯上看和重做⽇志⾮常相似,都是记录了对于数据库操作的⽇志,然⽽,从本质上来看,两者有⾮常⼤的不同
⾸先,重做⽇志是InnoDB存储引擎层产⽣,⽽⼆进制⽇志是在MySQL数据库的上层产⽣,并且⼆进制⽇志不仅仅针对InnoDB存储引
擎,MySQL数据库中任何存储引擎对于数据库的编个都会产⽣⼆进制⽇志
其次,两种⽇志记录的内容形式不⼀样,MySQL数据库上层的⼆进制⽇志是⼀种逻辑⽇志,其记录的是对应的SQL语句,⽽InnoDB存储引擎层⾯的重做⽇志是物理格式⽇志,其记录的是每个页的修改
此外,两种⽇志记录写⼊磁盘的时间点不同,如图,⼆进制⽇志只在事务提交完成后进⾏⼀次写⼊,⽽InnoDB存储引擎的重做⽇志在事务进⾏中不断地被写⼊,这表现为⽇志并不是随事务提交的顺序进⾏写⼊的
从图看到,⼆进制⽇志近在事务提交时记录,并且对每⼀个事务,仅包含对应事务的⼀个⽇志,⽽对于InnoDB存储引擎的重做⽇志,由于其记录的是物理操作⽇志,因此每个事务对应多个⽇志条⽬,并且事务的重做⽇志是并发的,并⾮在事务提交时写⼊,故其在⽂件中的记录顺序并⾮是事务的开始顺序。

*T1 * T2 *T3表⽰事务提交时的⽇志
2 log block
在InnoDB存储引擎中,重做⽇志都是以512字节进⾏存储的,这意味着重做⽇志缓存、重做⽇志⽂件块都是以块block的⽅式进⾏保存的,称为重做⽇志块(redo log block)每块的⼤⼩512字节
每个页中产⽣的重做⽇志数量⼤于512字节,那么需要分割多个重做⽇志块进⾏存储,此外,由于重做⽇志快的⼤⼩和磁盘扇区⼤⼩⼀样,都是512字节,因此重做⽇志的写⼊可以保证原⼦性,不需要double write技术
重做⽇志快除了⽇志本⾝之外,还由⽇志块头(log block header)及⽇志块尾(log block tailer)两部分组成。

重做⽇志头⼀共占⽤12字节,重做⽇志尾占⽤8字节。

故每个重做⽇志块实际可以存储的⼤⼩为492字节(512-12-8),如图显⽰重做⽇志块缓存的结构
如图显⽰了重做⽇志缓存的结果,可以发现,重做⽇志缓存由每个为512字节⼤⼩的⽇志块锁组成,⽇志块由三部分组成,依次为⽇志快头(log block header)、⽇志内容(log body)、⽇志块尾(log block tailer)
log block header由4部分组成
log buffer 是由log block组成,在内部log buffer就好似⼀个数组,因此LOG_BLOCK_HDR_NO⽤来标记这个数组中的位置,尤其是递增并且循环使⽤的。

占⽤4个字节。

但是由于第⼀位⽤来判断是否是flush bit,所以最⼤值为2G
LOG_BLOCK_HDR_DATA_LEN占⽤2个字节,表⽰log block所占⽤的⼤⼩,当log block被写满时,该值为0x200,表⽰使⽤全部的log block空间,即占⽤512字节
LOG_BLOCK_FIRST_REC_GROUP 占⽤2个字节,表⽰log block中第⼀个⽇志所在的偏移量。

如果该值的⼤⼩和
LOG_BLOCK_HDR_DATA_LEN相同,则表⽰当前log block不包含新的⽇志。

如事务T1的重做⽇志1占⽤762字节,事务T2的重做⽇志占⽤100字节,。

由于每个log block实际只能保存492字节,因此其在log buffer的情况应该如图所⽰
从图可以观察到,由于事务T1的重做⽇志占⽤792字节,因此需要占⽤两个log block。

左侧的log block中
LOG_BLOCK_FIRST_REC_GROUP为12,级log block中第⼀个⽇志的开始位置,在第⼆个log block中,由于包含了之前事务T1的重做⽇志,事务T2的⽇志才是log block中第⼀⽇志,因此该log block的LOG_BLOCK_FIRST_REC_GROUP为(270+12)
LOG_BLOCK_CHECKPOINT_NO占⽤4字节,表⽰该log block最后被写⼊时的检查点第4字节的值
log block tailer 只由1个部分组成,且值和LOG_BLOCK_HDR_NO相同,并在函数log_block_init中被初始化 LOG_BLOCK_TRL_NO ⼤⼩为4字节
3 log group
log group 重做⽇志组,其中有多个重做⽇志⽂件,虽然源码已经⽀持log group的景象功能,但是在ha_⽂件中禁⽌了该功能,因此,InnoDB存储引擎实际只由⼀个log group
log group是⼀个逻辑的概念,并没有⼀个实际的物理⽂件来表⽰log group信息,log group 由多个重做⽇志⽂件组成,每个log group中的⽇志⽂件是相同的,且在InnoDB 1.2版本之前,重做⽇志⽂件的总⼤⼩要⼩于4GB,从InnoDB 1.2版本开始重做⽇志⽂件的总⼤⼩限制提⾼为512GB,InnoSQL版本的InnoDB存储引擎在1.1版本就⽀持⼤于4GB的重做⽇志
重做⽇志⽂件中存储就是之前log buffer中保存的log block。

因此其也是根据块的⽅式进⾏物理存储的管理,每个块的⼤⼩与log block⼀样,同样为512字节,在InnoDB存储引擎运⾏过程中,log buffer根据⼀定的规则将内存中的log block刷新到磁盘。

这个规则具体是
事务提交时
当log buffer中有⼀半的内存空间已经被使⽤时
log checkpoint时
对于log block的写⼊追加在redo log file最后部分,当⼀个redo log file写满时,会接着写下⼀个redo log file,其使⽤的⽅式为round-robin
虽然log block总是在redo log file的最后部分进⾏写⼊,有的读者可能以为对redo log file的写⼊时顺序的,其实不是,因为redo log file除了保存log buffer刷新到磁盘的log block,还保存了⼀些其他的信息,这些信息⼀共占⽤2KB⼤⼩,即每个redo log file的前2KB的部分不保存log block信息,对于log group中的第⼀个redo log file,其前2KB的部分保存4个512字节⼤⼩的块,其中存放的内容为
需要特别注意,上述信息仅在每个log group的第⼀个redo log file中进⾏存储,log group中的其余redo log file仅保留这些空间,但不保存上述信息。

正因为保存了这些信息,就意味着对redo log file 的写⼊并不是完全顺序的。

因为其除了log block的写⼊操作,还需要更新前2KB 部分的信息,这些信息对于InnoDB存储引擎的恢复操作来说⾮常关键和重要,故log group与redo log file 之间的关系如下
在log filer header 后⾯的部分为InnoDB存储引擎保存的checkpoint(检查点)值,其设计时交替写⼊。

这样的设计避免了因介质失败⽽导致⽆法找到可⽤的checkpoint的情况
4 重做⽇志格式
不同的数据库操作会有对应的重做⽇志格式。

此外,由于InnoDB存储引擎的存储管理是基于页的,故其重做⽇志格式也是基于页的。

虽然有着不同的重做⽇志格式,但他们有着通⽤的头部格式,如图
通⽤的头部格式由⼀下3部分组成
redo_log_type 重做⽇志类型
space: 表空间ID
page_no 页的偏移量
之后是redo log body ,根据重做⽇志类型的不对,会有不同的存储内容,例如,对于页上记录的插⼊和删除操作,分别对应的如图的格式到InnoDB 1.2版本只是,⼀共有51中重做⽇志的类型。

随着功能不断增加,相信会加⼊更多的重做⽇志类型
5 LSN
LSN是Log Sequence Number的缩写,其代表的是⽇志序列号,在InnoDB存储引擎中,LSN占⽤8个字节,并且单调递增。

LSN的含义
重做⽇志的写⼊的总量
checkpoint的位置
页的版本
LSN表⽰事务写⼊重做⽇志字节的总量。

例如当前重做⽇志的LSN为1000,有⼀个事务T1写⼊了100字节的重做⽇志,那么LSN久变成1100,若⼜有事务T2写⼊200字节的重做⽇志,那么LSN久变为1300,课件LSN记录的是重做⽇志的总量,其单位为字节
LSN不仅记录在重做⽇志中,还存在每个页中,在每个页的头部,有⼀个值FIL_PAGE_LSN,记录了该页的LSN,在页中,LSN表⽰该页最后刷新时LSN的⼤⼩。

因为重做⽇志记录的是每个页的⽇志,因此页中的LSN可以判断页是否需要进⾏恢复操作。

例如,页P1的LSN诶10000,⽽数据库启动时,InnoDB检测到写⼊重做⽇志中的LSN为13000,并且事务已经提交,那么数据库需要进⾏恢复操作。

将重做⽇志应⽤到P1页中,同样的,对于重做⽇志中LSN⼩于P1页的LSN,不需要进⾏重做,因为P1页中的LSN标⽰已经被刷新到该位置
⽤户可以通过命令SHOW ENGINE INNODB STATUS查看LSN的情况
---
LOG
---
Log sequence number1087932358
Log flushed up to1087932358
Pages flushed up to1087932358
Last checkpoint at 1087932358
0 pending log writes, 0 pending chkp writes
8log i/o's done, 0.00 log i/o's/second
Log sequence number 表⽰当前的LSN
Log flushed up to 表⽰刷新到重做⽇志⽂件的LSN
Last checkpoint at 表⽰刷新到磁盘的LSN
虽然在上⾯的例⼦中,Log sequence number和Log flushed up to 值是相同的,但是在实际⽣产环境中,该值可能不同,因为在⼀个事务中从⽇志缓冲刷新到重做⽇志⽂件并不只是在事务提交时发⽣,每秒都会有从⽇志缓冲刷新到重做⽇志⽂件的动作,下⾯是在⽣成环境下重做⽇志的信息⽰例
---
LOG
---
Log sequence number187********
Log flushed up to187********
Pages flushed up to187********
Last checkpoint at 187********
在⽣成环境下Log sequence number Log flushed up to Last checkpoint at 三个值可能不同
6 恢复
InnoDB存储引擎在启动时不管上次数据运⾏是否正常关闭,都会尝试进⾏恢复操作,因为重做⽇志记录的是物理⽇志,因此恢复的速度⽐逻辑⽇志,如⼆进制⽇志要快的多,于此同时,InnoDB存储引擎⾃⾝也对恢复进⾏了⼀定程度的优化,如顺序读取及并⾏应⽤重做⽇志,这样可以进⼀步提⾼数据库恢复的速度
由于checkpoint表⽰已经刷新到磁盘页上的LSN,因此在恢复过程中仅需恢复checkpoint开始的⽇志部分。

对于图中的例⼦,当数据库在checkpoint的LSN为10 000时发⽣宕机,恢复操作仅恢复LSN 10000~13000范围内的⽇志
InnoDB存储引擎重做⽇志是物理⽇志,因此其恢复的速度较之⼆进制⽇志恢复快的多,例如对Insert操作,其记录的是每个页的变化,对于下⾯的表
CREATE TABLE t(a INT ,b INT,PRIMARY KEY(a),key(b));
若执⾏SQL语句
INSERT INTO t SELECT1,2;
由于需要对聚集索引页和辅助索引页进⾏操作,其记录的重做⽇志⼤致为
page(2,3),offset 32,value 1,2 #聚集索引
page(2,4),offset 64,value 2 #辅助索引
可以看到记录的是页的物理修改曹邹,若插⼊涉及B+树的split,可能会有更多的页需要记录⽇志。

此外,由于重做⽇志是物理⽇志,因此其是幂等的,幂等的概念如下
f(f(x))=f(x)
但INSERT操作在⼆进制⽇志不是幂等,重复执⾏可能会插⼊多条重复的记录,⽽上述INSERT操作的重做⽇志是幂等的。

相关文档
最新文档