InnoDB对事务的实现

首先,我们知道事务具有ACID四个特性,即:原子性,一致性,隔离性,持久性。这四个性质我们不用干瘪的文字去阐述,我们只需要知道事务保证了一系列的操作要么全部执行,要么一个也不执行,同时一旦事务提交,则其所做的修改会永久保存到数据库即可。

接下来我们一起看看InnoDB怎么实现的事务。

事务的隔离性通过锁或 MVCC 机制来实现,而原子性、持久性和一致性通过 redo/undo log 来完成。redo log 称为重做日志,用来保证事务的原子性和持久性。undo log 称为撤销日志,用来保证事务的一致性。本文重点关注Redo log 和Undo log。

1、redo log

1.1、基本概念

重做日志用来实现事务的持久性,由以下两部分组成:

  • 重做日志缓冲区(redo log buffer),内存中,易丢失。
  • 重做日志文件(redo log file),磁盘中,持久的。

redo log file 是顺序写入的,在数据库运行时不需要进行读取,只会在数据库启动的时候读取来进行数据的恢复工作。
redo log file 是物理日志,所谓的物理日志是指日志中的内容都是直接操作物理页的命令,重做时是对某个物理页进行相应的操作。

有内存和磁盘上的两个对应实体,我们就知道这样做一定是为了效率考虑,因为内存的读写效率要比磁盘读写效率高太多

更新事务操作一次数据更新的流程如下:
在这里插入图片描述

Innodb是支持事务的存储引擎,在事务提交时,必须先将该事务的所有日志写入到redo日志文件中,待事务的commit操作完成才算整个事务操作完成。在每次将redo log buffer写入redo log file后,都需要调用一次fsync操作,因为重做日志缓冲只是把内容先写入操作系统的缓冲系统中,并没有确保直接写入到磁盘上,所以必须进行一次fsync操作。因此,磁盘的性能在一定程度上也决定了事务提交的性能。

  1. 先将原始数据从磁盘中读入到内存中,修改数据的内存拷贝;
  2. 生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值;
  3. 必要的时候,采用追加写的方式将 redo log buffer 中的内容刷新到 redo log file;
  4. 定期将内存中修改的数据刷新到磁盘中

以上比较重要的是第三步,其中必要的时候有以下几种情况:

  • 事务提交时(最常见的情景,在 commit 之前)
  • 当 log buffer 中有一半的内存空间被使用时
  • log checkpoint 时
  • 实例 shutdown 时
  • binlog切换时
  • 后台线程

1.2、刷盘策略

学过 linux 操作系统的都知道内存中数据写入到磁盘文件中时如果不打开 O_DIRECT 选项。数据是要先写入文件操作系统缓存区中的,然后在某个时刻 flush 到磁盘。流程如下:
在这里插入图片描述
事务提交时将 redo log buffer 写入 redo log file,为了保证数据一定能正确同步到磁盘(不仅仅只写到文件缓冲区中)文件中,InndoDB 默认情况下调用了 fsync 进行写操作。而 fsync的性能比较低。当然这只是默认情况,InnoDB 也提供了参数 innodb_flush_log_at_trx_commit来配置 redo log 刷新到磁盘的策略,有以下三个值:

  • 当设置该值为 1 时,每次事务提交都要做一次 fsync,这是最安全的配置,即使宕机也不会丢失事务;
  • 当设置为 2 时,则在事务提交时只做 write 操作,只保证写到系统的缓冲区,因此实例crash不会丢失事务,但宕机则可能丢失事务;
  • 当设置为 0 时,事务提交不会触发 redo 写操作,而是留给后台线程每秒一次的刷盘操作,因此实例 crash 将最多丢失一秒钟内的事务。

用下图可以更直观的说明 innodb_flush_log_at_trx_commit 不同值下的不同策略。操作越接近磁盘性能越低,当然可靠性越来越高。故性能:1 < 2 < 0,可靠性:0 < 2 < 1。
在这里插入图片描述

当 innodb_flush_log_at_trx_commit 设置为 0 或者 2 时丧失了事务的 ACID 特性,通常在日常环境时将其设置为 1,而在系统高峰时将其设置为 2 以应对大负载。

1.3、恢复

在说从redo log file恢复之前,还要说一个LSN的概念,LSN是Log Sequence Number的缩写,其代表的是日志序列号,在InnoDB存储引擎中,LSN占用8个字节,并且单调递增。

LSN表示事务写入重做日志字节的总量。例如当前重做日志的LSN为1000,有一个事务T1写入了100字节的重做日志,那么LSN就变成1100,若又有事务T2写入200字节的重做日志,那么LSN就变为1300。

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

InnoDB 引擎启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作,整个恢复操作有如下特点:

  • 从 checkpoint 开始的日志部分进行恢复,checkpoint表示已经刷新到磁盘上的重做日志总量,因此恢复时只需要恢复从checkpoint开始的日志部分
  • 顺序读取及并行应用重做日志。
  • 重做日志的应用具有幂等性。
  • 重做日志是物理日志,恢复的速度相对较快。

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

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

在这里插入图片描述

2、undo log

2.1、基本概念

在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。Undo log可以实现如下两个功能:实现事务回滚及MVCC。

  • undo log 用来实现事务的一致性,InndoDB 可以通过 redo log 对页进行重做操作。但是有时候事务需要进行回滚,这时就需要 undo log。
  • undo log还可以用来协助InnoDB引擎实现 MVCC 机制。
  • undo log是逻辑日志,恢复时并不是对物理页直接进行恢复,而是逻辑地将数据库恢复到原来的样子。
  • undo log的产生也会伴随着redo log的产生。

undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。

2.2、写入时机

事务开始之前,流程如图:
在这里插入图片描述

2.3、日志格式

在 InnoDB中undo log分为insert undo log和 update undo log两类:

insert undo log

insert 操作产生的日志。根据隔离性,insert 插入的记录只对本事务可见,所以事务提交后可以删除因 insert 产生的日志。

update undo log

delete和update操作产生的日志,根据MVCC机制可以知道此部分记录还有可能要被其他事务所使用,所以即使事务提交也不能删除相应的日志。在事务提交时会被保存到undo log链表,在purge线程中做最后的删除。

当执行回滚时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。有时候应用到行版本控制的时候,也是通过undo log来实现的:当读取的某一行被其他事务锁定时,它可以从undo log中分析出该行记录以前的数据是什么,从而提供该行版本信息,帮助用户实现一致性非锁定读取。

undo 记录更新之前的日志,为了回滚。
redo 记录更新之后的日志,为了重做。
redo log 与 undo log 产生过程的简化版本如下,可以更方便的理解 redo 与 undo 的区别。

假设有A,B两个数据,原值分别为 1, 2;现将A更新为10,B 更新为 20;undo log记录信息过程如下:
1. 事务开始
2. 记录 A = 1 到 undo log
3. 更新 A = 10
4. 记录 A = 10 到 redo log
5. 记录 B = 2 到 undo log
6. 更新 B = 20
7. 记录 B = 20 到 redo log
8. redo log 信息写入磁盘
9. 提交事务

注意:如果undo log一直不删除,则会通过当前记录的回滚指针回溯到该行创建时的初始内容,所幸的时在Innodb中存在purge线程,它会查询那些比现在最老的活动事务还早的undo log,并删除它们,从而保证undo log文件不至于无限增长。

参考资料

https://blog.csdn.net/u012878327/article/details/91355492
https://blog.csdn.net/tangkund3218/article/details/47904021
https://cloud.tencent.com/developer/news/338659

发布了92 篇原创文章 · 获赞 447 · 访问量 46万+

猜你喜欢

转载自blog.csdn.net/fuzhongmin05/article/details/101294958