2021-04-11

MySQL 事务

摘自 MySQL技术内幕
事务(Transaction)是数据库区别于文件系统的重要特性之一。

在文件系统中,如果正在写文件,但是操作系统突然崩溃了,这个文件就很有可能被破坏。
当然,有一些机制可以把文件恢复到某个时间点。不过,如果需要保证两个文件同步,这些文件系统可能就显得无能为力了。

例如,在需要更新两个文件时,更新完一个文件后,在更新完第二个文件之前系统重启了,就会有两个不同步的文件。

这正是数据库系统引入事务的主要目的:
事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。

InnoDB 存储引擎中的事务完全符合 ACID 的特性
ACID 是以下 4 个词的缩写:

❑原子性(atomicity)

❑一致性(consistency)

❑隔离性(isolation)

❑持久性(durability)

认识事务

事务可由一条非常简单的 SQL 语句组成,也可以由一组复杂的 SQL 语句组成。

事务是访问并更新数据库中各种数据项的一个程序执行单元。

在事务中的操作,要么都做修改,要么都不做,这就是事务的目的

理论上说,事务有着极其严格的定义,它必须同时满足四个特性,即通常所说的事务的 ACID 特性。

值得注意的是,虽然理论上定义了严格的事务要求,但是数据库厂商出于各种目的,并没有严格去满足事务的 ACID 标准。

对于 InnoDB 存储引擎而言,其默认的事务隔离级别为 READ REPEATABLE,完全遵循和满足事务的 ACID 特性。

事务的 ACID 特性 (事务的四个特性)

A(Atomicity)原子性

整个取款的操作过程应该视为原子操作,即要么都做,要么都不做。
不能用户钱未从 ATM 机上取得,但是银行卡上的钱已经被扣除了,相信这是任何人都不能接受的一种情况。
而通过事务模型,可以保证该操作的原子性。

原子性指整个数据库事务是不可分割的工作单位。

只有使事务中所有的数据库操作都执行成功,才算整个事务成功。

事务中任何一个 SQL 语句执行失败,已经执行成功的 SQL 语句也必须撤销,数据库状态应该退回到执行事务前的状态。

如果事务中的操作都是只读的,要保持原子性是很简单的。
一旦发生任何错误,要么重试,要么返回错误代码。因为只读操作不会改变系统中的任何相关部分。

但是,当事务中的操作需要改变系统中的状态时,例如插入记录或更新记录,那么情
况可能就不像只读操作那么简单了。

如果操作失败,很有可能引起状态的变化,因此必须要保护系统中并发用户访问受影响的部分数据。

C(consistency)一致性

一致性: 指事务将数据库从一种状态 转变为 下一种 一致的状态。

在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

例如,在表中有一个字段为姓名,为唯一约束,即在表中姓名不能重复。
如果一个事务对姓名字段进行了修改,但是在事务提交或事务操作发生回滚后,表中的姓名变得非唯一了,这就破坏了事务的一致性要求,即事务将数据库从一种状态变为了一种不一致的状态。

因此,事务是一致性的单位,如果事务中某个动作失败了,系统可以自动撤销事务, 返回初始化的状态。

I(isolation)隔离性

隔离性, 又叫: 如并发控制(concurrency control)、可串行化(serializability)、锁(locking)等。

事务的 隔离性 要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常这使用锁来实现。

当前数据库系统中都提供了一种粒度锁(granular lock)的策略,允许事务仅锁住一个实体对象的子集,以此来提高事务之间的并发度。

D(durability)持久性

事务一旦提交,其结果就是永久性的。

即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,只能从事务本身的角度来保证结果的永久性。

例如,在事务提交后,所有的变化都是永久的。即使当数据库因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。

但若不是数据库本身发生故障,而是一些外部的原因,如 RAID 卡损坏、自然灾害等原因导致数据库发生问题,那么所有提交的数据可能都会丢失。

因此持久性保证事务系统的高可靠性(High Reliability),而不是高可用性(High Availability)。

对于高可用性的实现,事务本身并不能保证,需要一些系统共同配合来完成。

从事务理论的角度来说,可以把事务分为以下几种类型:

❑扁平事务(Flat Transactions)

❑带有保存点的扁平事务(Flat Transactions with Savepoints)

❑链事务(Chained Transactions)

❑嵌套事务(Nested Transactions)

❑分布式事务(Distributed Transactions)

扁平事务(Flat Transaction)是事务类型中最简单的一种,但在实际生产环境中,这可能是使用最为频繁的事务。

在扁平事务中,所有操作都处于同一层次,其由 BEGIN WORK 开始,由 COMMIT WORK 或 ROLLBACK WORK 结束,其间的操作是原子的,要么都执行,要么都回滚。

带有保存点的扁平事务(Flat Transactions with Savepoint),除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。

保存点(Savepoint)用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。

分布式事务(Distributed Transactions)通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。

1)节点 A 发出转账命令。

2)节点 B 执行储蓄卡中的余额值减去 10 000。

3)节点 C 执行储蓄卡中的余额值加上 10 000。

4)节点 A 通知用户操作完成或者节点 A 通知用户操作失败。

这里需要使用分布式事务,因为节点 A 不能通过调用一台数据库就完成任务。其需要访问网络中两个节点的数据库,而在每个节点的数据库执行的事务操作又都是扁平的。对于分布式事务,其同样需要满足 ACID 特性,要么都发生,要么都失效。对于上述的例子,如果 2)、3)步中任何一个操作失败,都会导致整个分布式事务回滚。若非这样,结果会非常可怕。

事务的实现

事务 隔离性 由 锁 来实现

原子性、一致性、持久性 通过数据库的 redo log 和 undo log 来完成

redo log 称为重做日志,用来保证事务的 原子性 和 持久性。

undo log 用来保证 事务的一致性。

redo 和 undo 的作用都可以视为是一种恢复操作,redo 恢复 提交事务修改 的页操作,而 undo 回滚 行记录 到某个特定版本。

因此两者记录的内容不同,

redo 通常是物理日志,记录的是页的物理修改操作。

undo 是逻辑日志,根据每行记录进行记录。

redo(redo log)

1. 基本概念

重做日志用来实现事务的持久性,即事务 ACID 中的 D。

其由两部分组成:
一是 内存中的重做日志缓冲(redo log buffer),其是易失的;
二是 重做日志文件(redo log file),其是持久的。

InnoDB 是事务的存储引擎,其通过 Force Log at Commit 机制实现事务的持久性,即 当事务提交(COMMIT)时,必须先将该事务的所有日志写入到 重做日志文件 (redo log 和 undo log) 进行持久化,该事务的 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 秒会进行一次重做日志文件的 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 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(500 000),则会向表中插入 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'

***************************1.row***************************

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 的次数,从而提高了数据库执行的性能。
表 7-1 显示了在参数 innodb_flush_log_at_trx_commit 的不同设置下,调用存储过程 p_load 插入 50 万行记录所需的时间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-um4vBIxt-1618149215843)(assets/2021-04-11-11-29-47.png)]

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

binlog

在 MySQL 数据库中还有一种二进制日志(binlog),其用来进行 POINT-IN-TIME(PIT)的恢复 及 主从复制(Replication)环境的建立。

从表面上看其和重做日志非常相似,都是记录了对于数据库操作的日志。然而,从本质上来看,两者有着非常大的不同。

首先,重做日志是在 InnoDB 存储引擎层产生,而二进制日志是在 MySQL 数据库的上层产生的,并且二进制日志不仅仅针对于 InnoDB 存储引擎,MySQL 数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。

其次,两种日志记录的内容形式不同。MySQL 数据库上层的二进制日志是一种逻辑日志,其记录的是对应的 SQL 语句。
而 InnoDB 存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改。

此外,两种日志记录写入磁盘的时间点不同,如图 7-6 所示。

二进制日志只在事务提交完成后进行一次写入。

而 InnoDB 存储引擎的重做日志在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写入的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LGLYYWAr-1618149215845)(assets/2021-04-11-11-33-29.png)]

图 7-6 二进制日志与重做日志的写入的时间点不同

从图 7-6 中可以看到,二进制日志仅在事务提交时记录,并且对于每一个事务,仅包含对应事务的一个日志。

而对于 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 显示了重做日志块缓存的结构。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5HypmL0m-1618149215849)(assets/2021-04-11-11-35-08.png)]

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

log block header 由 4 部分组成,如表 7-2 所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U1TH0Hy7-1618149215852)(assets/2021-04-11-11-35-17.png)]

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 中的情况应如图 7-8 所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R1HUC1ru-1618149215853)(assets/2021-04-11-11-35-30.png)]

图 7-8 LOG_BLOCK_FIRST_REC_GROUP 的例子

从图 7-8 中可以观察到,由于事务 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 个部分组成(如表 7-3 所示),且其值和 LOG_BLOCK_HDR_NO 相同,并在函数 log_block_init 中被初始化。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vXtM8wsT-1618149215854)(assets/2021-04-11-11-35-42.png)]

3.log group

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

log group 是一个逻辑上的概念,并没有一个实际存储的物理文件来表示 log group 信息。
log group 由多个重做日志文件组成,每个 log group 中的日志文件大小是相同的,

且在 InnoDB 1.2 版本之前,重做日志文件的总大小要小于 4GB(不能等于 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 的写入追加(append)在 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 字节大小的块,其中存放的内容如表 7-4 所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JxYmJZ0I-1618149215856)(assets/2021-04-11-11-36-04.png)]

需要特别注意的是,上述信息仅在每个 log group 的第一个 redo log file 中进行存储。log group 中的其余 redo log file 仅保留这些空间,但不保存上述信息。正因为保存了这些信息,就意味着对 redo log file 的写入并不是完全顺序的。因为其除了 log block 的写入操作,还需要更新前 2KB 部分的信息,这些信息对于 InnoDB 存储引擎的恢复操作来说非常关键和重要。故 log group 与 redo log file 之间的关系如图 7-9 所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LT2AJnyG-1618149215859)(assets/2021-04-11-11-36-20.png)]

图 7-9 log group 与 redo log file 之间的关系

在 log filer header 后面的部分为 InnoDB 存储引擎保存的 checkpoint(检查点)值,其设计是交替写入,这样的设计避免了因介质失败而导致无法找到可用的 checkpoint 的情况。

4. 重做日志格式

不同的数据库操作会有对应的重做日志格式。
此外,由于 InnoDB 存储引擎的存储管理是基于页的,故其重做日志格式也是基于页的。虽然有着不同的重做日志格式,但是它们有着通用的头部格式,如图 7-10 所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oFVPCout-1618149215860)(assets/2021-04-11-11-36-30.png)]

图 7-9 log group 与 redo log file 之间的关系

在 log filer header 后面的部分为 InnoDB 存储引擎保存的 checkpoint(检查点)值,其设计是交替写入,这样的设计避免了因介质失败而导致无法找到可用的 checkpoint 的情况。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xSW3KpEc-1618149215861)(assets/2021-04-11-11-36-43.png)]

图 7-10 重做日志格式

通用的头部格式由以下 3 部分组成:

❑redo_log_type:重做日志的类型。

❑space:表空间的 ID。

❑page_no:页的偏移量。

之后 redo log body 的部分,根据重做日志类型的不同,会有不同的存储内容,例如,对于页上记录的插入和删除操作,分别对应如图 7-11 所示的格式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zetZ7GxF-1618149215862)(assets/2021-04-11-11-36-59.png)]

图 7-11 插入和删除的重做日志格式

到 InnoDB1.2 版本时,一共有 51 种重做日志类型。随着功能不断地增加,相信会加入越来越多的重做日志类型。

5.LSN

LSN 是 Log Sequence Number 的缩写,其代表的是日志序列号。

在 InnoDB 存储引擎中,LSN 占用 8 字节,并且单调递增。LSN 表示的含义有:

❑重做日志写入的总量

❑checkpoint 的位置

❑页的版本

LSN 表示事务写入重做日志的字节的总量。

例如当前重做日志的 LSN 为 1 000,有一个事务 T1 写入了 100 字节的重做日志,那么 LSN 就变为了 1100,若又有事务 T2 写入了 200 字节的重做日志,那么 LSN 就变为了 1 300。

可见 LSN 记录的是重做日志的总量,其单位为字节。

LSN 不仅记录在重做日志中,还存在于每个页中。
在每个页的头部,有一个值 FIL_PAGE_LSN,记录了该页的 LSN。
在页中,LSN 表示该页最后刷新时 LSN 的大小。
因为重做日志记录的是每个页的日志,因此页中的 LSN 用来判断页是否需要进行恢复操作。
例如,页 P1 的 LSN 为 10 000,而数据库启动时,InnoDB 检测到写入重做日志中的 LSN 为 13 000,并且该事务已经提交,那么数据库需要进行恢复操作,将重做日志应用到 P1 页中。同样的,对于重做日志中 LSN 小于 P1 页的 LSN,不需要进行重做,因为 P1 页中的 LSN 表示页已经被刷新到该位置。

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

mysql>SHOW ENGINE INNODB STATUS\G;

……

---

LOG

---

Log sequence number 11 3047174608

Log flushed up to 11 3047174608

Last checkpoint at 11 3047174608

0 pending log writes,0 pending chkp writes

142 log i/o's done,0.00 log i/o's/second

……

1 row in set(0.00 sec)

Log sequence number 表示当前的 LSN
Log flushed up to 表示刷新到重做日志文件的 LSN
Last checkpoint at 表示刷新到磁盘的 LSN

虽然在上面的例子中,Log sequence number 和 Log flushed up to 的值是相同的,但是在实际生产环境中,该值有可能是不同的。

因为在一个事务中从日志缓冲刷新到重做日志文件并不只是在事务提交时发生,每秒都会有从日志缓冲刷新到重做日志文件的动作。

下面是在生产环境下重做日志的信息的示例。

mysql>show engine innodb status\G;

---

LOG

---

Log sequence number 203318213447

Log flushed up to 203318213326

Last checkpoint at 203252831194

1 pending log writes,0 pending chkp writes

103447 log i/o's done,7.00 log i/o's/second

……

1 row in set(0.00 sec)

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

6. 恢复

InnoDB 存储引擎在启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作。

因为重做日志记录的是物理日志,因此恢复的速度比逻辑日志,如二进制日志,要快很多。

与此同时,InnoDB 存储引擎自身也对恢复进行了一定程度的优化,如顺序读取及并行应用重做日志,这样可以进一步地提高数据库恢复的速度。

由于 checkpoint 表示已经刷新到磁盘页上的 LSN,因此在恢复过程中仅需恢复 checkpoint 开始的日志部分。

对于图 7-12 中的例子,当数据库在 checkpoint 的 LSN 为 10 000 时发生宕机,恢复操作仅恢复 LSN 10 000~13 000 范围内的日志。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MiOHkfhB-1618149215863)(assets/2021-04-11-13-04-32.png)]

图 7-12 恢复的例子

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

上一篇:pipeline中sh使用参数化构建输入的参数


下一篇:3.电商工具 CefSharp AutoJs Mysql 阿里云 React C# RPA 自动化脚本,开源日志