关于mysql:MySQL中事务的持久性实现原理

62次阅读

共计 4307 个字符,预计需要花费 11 分钟才能阅读完成。

前言

说到数据库事务,大家脑子里肯定很容易蹦出一堆事务的相干常识,如事务的 ACID 个性,隔离级别,解决的问题(脏读,不可反复读,幻读)等等,然而可能很少有人真正的分明事务的这些个性又是怎么实现的,为什么要有四个隔离级别。

在之前的文章咱们曾经理解了 MySQL 中事务的隔离性的实现原理,明天就持续来聊一聊 MySQL 持久性的实现原理。

当然 MySQL 博大精深,文章疏漏之处在劫难逃,欢送批评指正。

阐明

MySQL 的事务实现逻辑是位于引擎层的,并且不是所有的引擎都反对事务的,上面的阐明都是以 InnoDB 引擎为基准。

InnoDB 读写数据原理

在往下学习之前,咱们须要先来理解下 InnoDB 是怎么来读写数据的。咱们晓得数据库的数据都是寄存在磁盘中的,而后咱们也晓得磁盘 I / O 的老本是很大的,如果每次读写数据都要拜访磁盘,数据库的效率就会非常低。为了解决这个问题,InnoDB 提供了 Buffer Pool 作为拜访数据库数据的缓冲。

Buffer Pool 是位于内存的,蕴含了磁盘中局部数据页的映射。当须要读取数据时,InnoDB 会首先尝试从 Buffer Pool 中读取,读取不到的话就会从磁盘读取后放入 Buffer Pool;当写入数据时,会先写入 Buffer Pool 的页面,并把这样的页面标记为 dirty,并放到专门的 flush list 上,这些批改的数据页会在后续某个时刻被刷新到磁盘中(这一过程称为刷脏,由其余后盾线程负责)。如下图所示:

这样设计的益处是能够把大量的磁盘 I / O 转成内存读写,并且把对一个页面的屡次批改 merge 成一次 I / O 操作(刷脏一次刷入整个页面),防止每次读写操作都拜访磁盘,从而大大晋升了数据库的性能。

持久性定义

持久性是指事务一旦提交,它对数据库的扭转就应该是永久性的,接下来的其余操作或故障不应该对本次事务的批改有任何影响。

通过后面的介绍,咱们晓得 InnoDB 应用 Buffer Pool 来进步读写的性能。然而 Buffer Pool 是在内存的,是易失性的,如果一个事务提交了事务后,MySQL 忽然宕机,且此时 Buffer Pool 中批改的数据还没有刷新到磁盘中的话,就会导致数据的失落,事务的持久性就无奈保障。

为了解决这个问题,InnoDB 引入了 redo log 来实现数据批改的长久化。当数据批改时,InnoDB 除了批改 Buffer Pool 中的数据,还会在 redo log 记录这次操作,并保障 redo log 早于对应的页面落盘(个别在事务提交的时候),也就是常说的 WAL。若 MySQL 忽然宕机了且还没有把数据刷回磁盘,重启后,MySQL 会通过曾经写入磁盘的 redo log 来复原没有被刷新到磁盘的数据页。

实现原理:redo log

为了进步性能,和数据页相似,redo log 也包含两局部:一是内存中的日志缓冲(redo log buffer),该局部日志是易失性的;二是磁盘上的重做日志文件(redo log file),该局部日志是长久的。redo log 是物理日志,记录的是数据库中物理页的状况。

当数据产生批改时,InnoDB 不仅会批改 Buffer Pool 中的数据,也会在 redo log buffer 记录这次操作;当事务提交时,会对 redo log buffer 进行刷盘,记录到 redo log file 中。如果 MySQL 宕机,重启时能够读取 redo log file 中的数据,对数据库进行复原。这样就不须要每次提交事务都实时进行刷脏了。

写入过程

留神点:

  • 先批改 Buffer Pool,后写 redo log buffer。
  • redo 日志比数据页先写回磁盘:事务提交的时候,会把 redo log buffer 写入 redo log file,写入胜利才算提交胜利(也有其余场景触发写入,这里就不开展了),而 Buffer Pool 的数据由后盾线程在后续某个时刻写入磁盘。
  • 刷脏的时候肯定会保障对应的 redo log 曾经落盘了,也即是所谓的 WAL(预写式日志),否则会有数据失落的可能性。

益处

事务提交的时候,写入 redo log 相比于间接刷脏的益处次要有三点:

  • 刷脏是随机 I /O,但写 redo log 是程序 I /O,程序 I / O 可比随机 I / O 快多了,不须要。
  • 刷脏是以数据页(Page)为单位的,即便一个 Page 只有一点点批改也要整页写入;而 redo log 中只蕴含真正被批改的局部,数据量十分小,有效 IO 大大减少。
  • 刷脏的时候可能要刷很多页的数据,无奈保障原子性(例如只写了一部分数据就失败了),而 redo log buffer 向 redo log file 写 log block,是按 512 个字节,也就是一个扇区的大小进行写入,扇区是写入的最小单位,因而能够保障写入是必然胜利的。

先写 redo log 还是先批改数据

一次 DML 可能波及到数据的批改和 redo log 的记录,那它们的执行程序是怎么样的呢?网上的文章有的说先批改数据,后记录 redo log,有的说先记录 redo log,后改数据,那实在的状况是如何呢?

首先通过下面的阐明咱们晓得,redo log buffer 在事务提交的时候就会写入 redo log file 的,而刷脏则是在后续的某个时刻,所以能够确定的是 先记录 redo log,后批改 data page(WAL 当然是日志先写啦)。

那接下来的问题就是先写 redo log buffer 还是先批改 Buffer Pool 了。要理解这个问题,咱们先要理解 InnoDB 中,一次 DML 的执行过程是怎么样的。一次 DML 的执行过程波及了数据的批改,加锁,解锁,redo log 的记录和 undo log 的记录等,也是须要保障原子性的,而 InnoDB 通过 MTR(Mini-transactions)来保障一次 DML 操作的原子性。

首先来看 MTR 的定义:

An internal phase of InnoDB processing, when making changes at the physical level to internal data structures during DML operations. A Mini-transactions (mtr) has no notion of rollback; multiple Mini-transactionss can occur within a single transaction. Mini-transactionss write information to the redo log that is used during crash recovery. A Mini-transactions can also happen outside the context of a regular transaction, for example during purge processing by background threads.

见 https://dev.mysql.com/doc/ref…

MTR 是一个短原子操作,不能回滚,因为它自身就是原子的。数据页的变更必须通过 MTR,MTR 会把 DML 操作对数据页的批改记录到 redo log 里。

上面来简略看下 MTR 的过程:

  • MTR 初始化的时候会初始化一份 mtr_buf
  • 当批改数据时,在对内存 Buffer Pool 中的页面进行批改的同时,还会生成 redo log record,保留在 mtr_buf 中
  • 在执行 mtr_commit 函数提交本 MTR 的时候,会将 mtr_buf 中的 redo log record 更新到 redo log buffer 中,同时将脏页增加到 flush list,供后续刷脏应用。在 log buffer 中,每接管到 496 字节的 log record,就将这组 log record 包装一个 12 字节的 block header 和一个 4 字节的 block tailer,成为一个 512 字节的 log block,不便刷盘的时候对齐 512 字节刷盘。

由此可见,InnoDB 是先批改 Buffer Pool,后写 redo log buffer 的。

复原数据的过程

在任何状况下,InnoDB 启动时都会尝试执行 recovery 操作。在复原过程中,须要 redo log 参加,而如果还开启了 binlog,那就还须要 binlog、undo log 的参加。因为有可能数据曾经写入 binlog 了,然而 redo log 还没有刷盘的时候数据库就奔溃了(事务是 InnoDB 引擎的个性,批改了数据不肯定提交了,而 binlog 是 MySQL 服务层的个性,批改数据就会记录了),这时候就须要 redo log,binlog 和 undo log 三者的参加来判断是否有还没提交的事务,未提交的事务进行回滚或者提交操作。

上面来简略说下仅利用 redo log 复原数据的过程:

  • 启动 InnoDB 时,找到最近一次 Checkpoint 的地位,利用 Checkpoint LSN 去找大于该 LSN 的 redo log 进行日志复原。
  • 如果两头复原失败了也没影响,再次复原的时候还是从上次保留胜利的 Checkpoint 的地位持续复原。

Recover 过程:故障复原蕴含三个阶段:Analysis,Redo 和 Undo。Analysis 阶段的工作次要是利用 Checkpoint 及 Log 中的信息确认后续 Redo 和 Undo 阶段的操作范畴,通过 Log 修改 Checkpoint 中记录的 Dirty Page 汇合信息,并用其中波及最小的 LSN 地位作为下一步 Redo 的开始地位 RedoLSN。同时修改 Checkpoint 中记录的沉闷事务汇合(未提交事务),作为 Undo 过程的回滚对象;Redo 阶段从 Analysis 取得的 RedoLSN 登程,重放所有的 Log 中的 Redo 内容,留神这里也蕴含了未 Commit 事务;最初 Undo 阶段对所有未提交事务利用 Undo 信息进行回滚,通过 Log 的 PrevLSN 能够程序找到事务所有须要回滚的批改。

具体见 http://catkang.github.io/2019…

什么是 LSN?

LSN 也就是 log sequence number,也日志的序列号,是一个枯燥递增的 64 位无符号整数。redo log 和数据页都保留着 LSN,能够用作数据恢复的根据。LSN 更大的示意所援用的日志记录所形容的变动产生在更前面。

什么是 Checkpoint?

Checkpoint 示意一个保留点,在这个点之前的数据页的批改(log LSN<Checkpoint LSN)都曾经写入磁盘文件了。InnoDB 每次刷盘之后都会记录 Checkpoint,把最新的 redo log LSN 记录到 Checkpoint LSN 里,不便复原数据的时候作为起始点的判断。

版权申明

转载请注明作者和文章出处
作者: X 学生
https://segmentfault.com/a/1190000037671950

感觉不错的话请帮忙珍藏点赞~

正文完
 0