【MySQL技术内幕】7.2.1-事务的实现之redo log

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: 【MySQL技术内幕】7.2.1-事务的实现之redo log

事务隔离性由锁来实现。原子性、一致性、持久性通过数据库的redo log和undo log来完成。redo log称为重做日志,用来保证事务的原子性和持久性。undo log用来保证事务的一致性。

有的DBA或许会认为undo是redo的逆过程,其实不然。redo和undo的作用都可以视为是一种恢复操作,redo恢复提交事务修改的页操作,而undo回滚行记录到某个特定版本。因此两者记录的内容不同,redo通常是物理日志,记录的是页的物理修改操作。undo是逻辑日志,根据每行记录进行记录。

1、基本概念

重做日志用来实现事务的持久性,即事务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中每1秒会进行一次重做日志文件的fync操作。2表示事务提交时将重做日志写入重做日志文件,但仅写入文件系统的缓存中,不进行fync操作。在这个设置下,当 MySQL数据库发生宕机而操作系统不发生宕机时,并不会导致事务的丢失而当操作系统宕机时,重启数据库后会丢失未从文件系统缓存刷新到重做日志文件那部分事务。

下面看一个例子,比较innodb_flush_log_at_trx_commit对事务的影响。首先根据如下代码创建表t和存储过程 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 DEFAULT 1
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(500000),则会向表中插入50万行的记录,并执行50万次的 fsync操作。先看在默认情况插入50万条记录所需的时间下:

mysql> CALL p_load(500000);
Query OK, 0 rows affected (1 min 53.11 sec)复制代码

可以看到插入50万条记录差不多需要2分钟的时间。对于生产环境的用户来说,这个时间显然是不能接受的。而造成时间比较长的原因就在于 fsync操作所需的时间。

接着来看将参数innodb_flush_log_at_trx_commit设置为0的情况

mysql> SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'\G
Variable_name: innodb_flush_log_at_trx_commit
value:0
1 row in set (0.00 sec)
mysql> CALL p_load(500000);
Query OK, 0 rows affected (13.90 sec)复制代码

可以看到将参数innodb_flush_log_at_trx_commit设置为0后,插入50万行记录的时间缩短为了13.90秒,差不多是之前的12%。而形成这个现象的主要原因是:后者大大减少了fsync的次数,从而提高了数据库执行的性能。下表显示了在参数innodb_flush_log_at_trx_commit的不同设置下,调用存储过程p_load插入50万行记录所需的时间。

不同innodb_flush_log_at_trx_commit设置对于插入的速度影响

innodb_flush_log_at_trx_commit 执行所用时间
0 13.90秒
1 1分53.11秒
2 23.37秒

虽然用户可以通过设置参数innodb_flush_log_at_trx_commit为0或2来提高事务提交的性能,但是需要牢记的是,这种设置方法丧失了事务的ACID特性。而针对上述存储过程,为了提高事务的提交性能,应该在将50万行记录插入表后进行一次的COMMIT操作,而不是在每插入一条记录后进行一次 COMMIT操作。这样做的好处是还可以使事务方法在回滚时回滚到事务最开始的确定状态。

在 MySQL数据库中还有一种二进制日志(binlog),其用来进行 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字节,因此重做日志的写入可以保证原子性,不需要doublewrite技术。

重做日志块除了日志本身之外,还由日志块头(log block header)及日志块尾(log block tailer)两部分组成。重做日志头一共占用12字节,重做日志尾占用8字节。故每个重做日志块实际可以存储的大小为492字节(512-12-8)。下图显示了重做日志块缓存的结构。

图7-7显示了重做日志缓存的结构,可以发现重做日志缓存由每个为512字节大小的日志块所组成。日志块由三部分组成,依次为日志块头(log block header)、日志内容(log body)、日志块尾(log block tailer)。

log block header由4部分组成,如下表所示。

log block header

名称 占用字节
LOG_BLOCK_HDR_NO 4
LOG_BLOCK_HDR_DATA_LEN 2
LOG_BLOCK_FIRST_REC_GROUP 2
LOG_BLOCK_CHECKPOINT_NO 4

log buffer是由 log block组成,在内部 log buffer就好似一个数组,因此LOG_BLOCK_HDR_NO用来标记这个数组中的位置。其是递增并且循环使用的,占用4个字节,但是由于第一位用来判断是否是 flush bit,所以最大的值为2G。

注意:在innodb1.2.x版本之前,重做日志文件总的大小不得大于等于4G,而1.2.x版本将该限制扩大到了521G。

LOG_BLOCK_HDR_DATA_LEN占用2字节,表示 log block所占用的大小,当log blok被写满时,该值为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为282(270+12)。

LOG_BLOCK_CHECKPOINT_NO占用4字节,表示该 log block最后被写入时的检查点第4字节的值。

log block tailer只由1个部分组成(如下表所示),且其值和LOG_BLOCK_HDR_NO相同,并在函数log block init中被初始化。

log block tailer部分

名称 大小(字节)
LOG_BLOCK_TRL_NO 4

3、log group

log group为重做日志组,其中有多个重做日志文件。虽然源码中已支持log group的镜像功能,但是在ha_ innobase.cc文件中禁止了该功能。因此InnoDB存储引擎实际只有一个 log group。

log group是一个逻辑上的概念,并没有一个实际存储的物理文件来表示 log group信息。 log group由多个重做日志文件组成,每个 log group中的日志文件大小是相同的,且在 InnoDB1.2版本之前,重做日志文件的总大小要小于4GB(不能等于4GB)。

从 InnodB1.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的写入追加( append)在 redo log file的最后部分,当一个 redo logfle被写满时,会接着写入下一个 redo log file,其使用方式为 round-robin。

虽然 log block总是在 redo log file的最后部分进行写人,有的读者可能以为对redo log file的写入都是顺序的。其实不然,因为 redo log file除了保存 log buffer刷新到磁盘的log block,还保存了一些其他的信息,这些信息一共占用2KB大小,即每个redo log fle的前2KB的部分不保存log block的信息。对于 log group中的第一个 redo log file,其前2KB的部分保存4个512字节大小的块,其中存放的内容如下表所示。

redo log file前2KB部分的内容

名称 大小(字节)
log file header 512
checkpoint1 512
512
checkpoint2 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的部分,根据重做日志类型的不同,会有不同的存储内容,例如,对于页上记录的插入和删除操作,分别对应如下图所示的格式:

到 InnoDE1.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,并且该事务已经提交,那么数据库需要进行恢复操作,将重做日志应用到Pl页中。同样的,对于重做日志中LSN小于P1页的LSN,不需要进行重做为P1页中的LSN表示页已经被刷新到该位置。

用户可以通过命令 SHOW ENGINE INNODB STATUS查看LSN的情况:

Log sequence number表示当前的LSN, Log flushed up to表示刷新到重做日志文件的LSN, Last checkpoint at表示刷新到磁盘的LSN。虽然在上面的例子中, Log sequence number和 Log flushed up to的值是相同的,但是在实际生产环境中,该值有可能是不同的。因为在一个事务中从日志缓冲刷新到重做日志文件并不只是在事务提交时发生,每秒都会有从日志缓冲刷新到重做日志文件的动作。下面是在生产环境下重做日志的信息的示例。

可以看到,在生产环境下 Log sequence number、 Log fiushed up to、 Last checkpoint at三个值可能是不同的。

6、恢复

InnoDB存储引擎在启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作因为重做日志记录的是物理志,因此恢复的速度比逻辑日志,如二进制日志要快很多。与此同时, InnoDB存储引擎自身也对恢复进行了一定程度的优化,如顺序读取及并行应用重做日志,这样可以进一步地提高数据库恢复的速度。

由于checkpoint表示已经刷新到磁盘页上的LSN,因此在恢复过程中仅需恢复checkpoint开始的日志部分。对于下图中的例子,当数据库在 checkpoint的LSN为10000时发生宕机,恢复操作仅恢复LSN10000~13000范围内的日志。

InnoDB存储引擎的重做日志是物理日志,因此其恢复速度较之二进制日志恢复快得多。例如对于 INSERT操作,其记录的是每个页上的变化。对于下面的表:

CREATE TABLE t(a INT, b INT, PRIMARY KEY(a), KEY(b));

若执行SQL语句:

INSERT INTO t SELECT 1,2;

由于需要对聚集索引页和辅助索引页进行操作,其记录的重做日志大致为:

page(2,3), offset 32, value 1,2  #聚集索引

page(2,4), offset 64, value 2     #辅助索引

可以看到记录的是页的物理修改操作,若插人涉及B+树的 split,可能会有更多的页需要记录日志。此外,由于重做日志是物理日志,因此其是幂等的。幂等的概念如下

f(f(x))=f(x)

有的DBA或开发人员错误地认为只要将二进制日志的格式设置为ROW,那么二进制日志也是幂等的。这显然是错误的,举个简单的例子, INSERT操作在二进制日志中就不是幂等的,重复执行可能会插人多条重复的记录。而上述 INSERT操作的重做日志是幂等的。



相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
4天前
|
SQL 关系型数据库 MySQL
MySQL事务日志-Undo Log工作原理分析
事务的持久性是交由Redo Log来保证,原子性则是交由Undo Log来保证。如果事务中的SQL执行到一半出现错误,需要把前面已经执行过的SQL撤销以达到原子性的目的,这个过程也叫做"回滚",所以Undo Log也叫回滚日志。
MySQL事务日志-Undo Log工作原理分析
|
14天前
|
SQL 安全 关系型数据库
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
事务是MySQL中一组不可分割的操作集合,确保所有操作要么全部成功,要么全部失败。本文利用SQL演示并总结了事务操作、事务四大特性、并发事务问题、事务隔离级别。
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
|
16天前
|
安全 关系型数据库 MySQL
MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!
《MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!》介绍了MySQL中的三种关键日志:二进制日志(Binary Log)、重做日志(Redo Log)和撤销日志(Undo Log)。这些日志确保了数据库的ACID特性,即原子性、一致性、隔离性和持久性。Redo Log记录数据页的物理修改,保证事务持久性;Undo Log记录事务的逆操作,支持回滚和多版本并发控制(MVCC)。文章还详细对比了InnoDB和MyISAM存储引擎在事务支持、锁定机制、并发性等方面的差异,强调了InnoDB在高并发和事务处理中的优势。通过这些机制,MySQL能够在事务执行、崩溃和恢复过程中保持
43 3
|
19天前
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(04)事务隔离级别、AICD、CAP、BASE原则一直搞不懂? | 看这篇就够了
本文详细介绍了数据库事务的四大特性(AICD原则),包括原子性、隔离性、一致性和持久性,并深入探讨了事务并发问题与隔离级别。同时,文章还讲解了分布式系统中的CAP理论及其不可能三角关系,以及BASE原则在分布式系统设计中的应用。通过具体案例和图解,帮助读者理解事务处理的核心概念和最佳实践,为应对相关技术面试提供了全面的知识准备。
|
2月前
|
关系型数据库 MySQL 数据库
MySQL事务隔离级别及默认隔离级别的设置
在数据库系统中,事务隔离级别是一个关键的概念,它决定了事务在并发执行时如何相互隔离。MySQL提供了四种事务隔离级别,每种级别都解决了不同的并发问题。本文将详细介绍这些隔离级别以及MySQL的默认隔离级别。
|
2月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
504 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
26天前
|
监控 安全 Apache
什么是Apache日志?为什么Apache日志分析很重要?
Apache是全球广泛使用的Web服务器软件,支持超过30%的活跃网站。它通过接收和处理HTTP请求,与后端服务器通信,返回响应并记录日志,确保网页请求的快速准确处理。Apache日志分为访问日志和错误日志,对提升用户体验、保障安全及优化性能至关重要。EventLog Analyzer等工具可有效管理和分析这些日志,增强Web服务的安全性和可靠性。
|
3月前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
371 3
|
1月前
|
存储 监控 安全
什么是事件日志管理系统?事件日志管理系统有哪些用处?
事件日志管理系统是IT安全的重要工具,用于集中收集、分析和解释来自组织IT基础设施各组件的事件日志,如防火墙、路由器、交换机等,帮助提升网络安全、实现主动威胁检测和促进合规性。系统支持多种日志类型,包括Windows事件日志、Syslog日志和应用程序日志,通过实时监测、告警及可视化分析,为企业提供强大的安全保障。然而,实施过程中也面临数据量大、日志管理和分析复杂等挑战。EventLog Analyzer作为一款高效工具,不仅提供实时监测与告警、可视化分析和报告功能,还支持多种合规性报告,帮助企业克服挑战,提升网络安全水平。
|
3月前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1755 14
MySQL事务日志-Redo Log工作原理分析