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

简介: 【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操作的重做日志是幂等的。



相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
缓存 关系型数据库 MySQL
在MySQL中处理高并发和负载峰值的关键技术与策略
采用上述策略和技术时,每个环节都要进行细致的规划和测试,确保数据库系统既能满足高并发的要求,又要保持足够的灵活性来应对各种突发的流量峰值。实施时,合理评估和测试改动对系统性能的影响,避免单一措施可能引起的连锁反应。持续的系统监控和分析将对维护系统稳定性和进行未来规划提供重要信息。
518 15
|
数据库 文件存储 数据安全/隐私保护
YashanDB redo日志文件管理
YashanDB的redo日志文件用于记录数据库物理日志,支持宕机重演和主备复制。 redo日志有4种状态:NEW(新创建)、CURRENT(当前写入)、ACTIVE(未归档或未写盘)和INACTIVE(可复用)。可通过V$LOGFILE视图或直接查看$YASDB_DATA/dbfiles目录来管理redo日志。此外,支持添加、切换和删除redo日志以优化性能或应对磁盘故障等情况,但需注意仅能删除INACTIVE或NEW状态的日志以确保数据安全。
|
SQL 存储 关系型数据库
简单聊聊MySQL的三大日志(Redo Log、Binlog和Undo Log)各有什么区别
在MySQL数据库管理中,理解Redo Log(重做日志)、Binlog(二进制日志)和Undo Log(回滚日志)至关重要。Redo Log确保数据持久性和崩溃恢复;Binlog用于主从复制和数据恢复,记录逻辑操作;Undo Log支持事务的原子性和隔离性,实现回滚与MVCC。三者协同工作,保障事务ACID特性。文章还详细解析了日志写入流程及可能的异常情况,帮助深入理解数据库日志机制。
2044 0
|
存储 SQL 关系型数据库
mysql的undo log、redo log、bin log、buffer pool
MySQL的undo log、redo log、bin log和buffer pool是确保数据库高效、安全和可靠运行的关键组件。理解这些组件的工作原理和作用,对于优化数据库性能和保障数据安全具有重要意义。通过适当的配置和优化,可以显著提升MySQL的运行效率和数据可靠性。
297 4
|
监控 容灾 算法
阿里云 SLS 多云日志接入最佳实践:链路、成本与高可用性优化
本文探讨了如何高效、经济且可靠地将海外应用与基础设施日志统一采集至阿里云日志服务(SLS),解决全球化业务扩展中的关键挑战。重点介绍了高性能日志采集Agent(iLogtail/LoongCollector)在海外场景的应用,推荐使用LoongCollector以获得更优的稳定性和网络容错能力。同时分析了多种网络接入方案,包括公网直连、全球加速优化、阿里云内网及专线/CEN/VPN接入等,并提供了成本优化策略和多目标发送配置指导,帮助企业构建稳定、低成本、高可用的全球日志系统。
1416 55
|
监控 Java 应用服务中间件
Tomcat log日志解析
理解和解析Tomcat日志文件对于诊断和解决Web应用中的问题至关重要。通过分析 `catalina.out`、`localhost.log`、`localhost_access_log.*.txt`、`manager.log`和 `host-manager.log`等日志文件,可以快速定位和解决问题,确保Tomcat服务器的稳定运行。掌握这些日志解析技巧,可以显著提高运维和开发效率。
1804 13
|
缓存 Java 编译器
|
存储 缓存 关系型数据库
图解MySQL【日志】——Redo Log
Redo Log(重做日志)是数据库中用于记录数据页修改的物理日志,确保事务的持久性和一致性。其主要作用包括崩溃恢复、提高性能和保证事务一致性。Redo Log 通过先写日志的方式,在内存中缓存修改操作,并在适当时候刷入磁盘,减少随机写入带来的性能损耗。WAL(Write-Ahead Logging)技术的核心思想是先将修改操作记录到日志文件中,再择机写入磁盘,从而实现高效且安全的数据持久化。Redo Log 的持久化过程涉及 Redo Log Buffer 和不同刷盘时机的控制参数(如 `innodb_flush_log_at_trx_commit`),以平衡性能与数据安全性。
981 5
图解MySQL【日志】——Redo Log

推荐镜像

更多