详解MySQL中事务的持久性实现原理

前言

说到数据库事务,大家脑子里一定很容易蹦出一堆事务的相关知识,如事务的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/refman/8.0/en/glossary.html

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/01/16/crash-recovery.html

什么是LSN?

LSN也就是log sequence number,也日志的序列号,是一个单调递增的64位无符号整数。redo log和数据页都保存着LSN,可以用作数据恢复的依据。LSN更大的表示所引用的日志记录所描述的变化发生在更后面。

什么是Checkpoint?

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

以上就是详解MySQL中事务的持久性实现原理的详细内容,更多关于MySQL 事务的持久性的资料请关注我们其它相关文章!

(0)

相关推荐

  • MySQL 主从同步,事务回滚的实现原理

    BinLog BinLog是记录所有数据库表结构变更(例如create.alter table)以及表数据修改(insert.update.delete)的二进制日志,主从数据库同步用到的都是BinLog文件.BinLog日志文件有三种模式. STATEMENT 模式 内容:binlog 只会记录引起数据变更的 sql 语句 优势:该模式下,因为没有记录实际的数据,所以日志量和 IO 都消耗很低,性能是最优的 劣势:但有些操作并不是确定的,比如 uuid() 函数会随机产生唯一标识,当依赖 bi

  • MySQL 事务autocommit自动提交操作

    MySQL默认操作模式就是autocommit自动提交模式.这就表示除非显式地开始一个事务,否则每个查询都被当做一个单独的事务自动执行.我们可以通过设置autocommit的值改变是否是自动提交autocommit模式. 通过以下命令可以查看当前autocommit模式 mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+----

  • MySQL数据库事务与锁深入分析

    一.基本概念 事务是指满足ACID特性的的一组操作,可以通过Commit提交事务,也可以也可以通过Rollback进行回滚.会存在中间态和一致性状态(也是真正在数据库表中存在的状态) 二.ACID Atomicity[原子性]:事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚.回滚可以用回滚日志(undo Log)来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可 undoLog:为了满足事务的原子性,在操作任何数据之前,首先将数据备份到U

  • MySQL为什么要避免大事务以及大事务解决的方法

    什么是大事务 运行时间比较长,长时间未提交的事务就可以称为大事务 大事务产生的原因 操作的数据比较多 大量的锁竞争 事务中有其他非DB的耗时操作 ... 大事务造成的影响 并发情况下,数据库连接池容易被撑爆 锁定太多的数据,造成大量的阻塞和锁超时 执行时间长,容易造成主从延迟 回滚所需要的时间比较长 undo log膨胀 ... 如何查询大事务 **注**:本文的sql的操作都是基于mysql5.7版本 以查询执行时间超过10秒的事务为例: select \* from information\

  • Mysql事务中Update是否会锁表?

    两种情况: 1.带索引 2.不带索引 前提介绍: 方式:采用命令行的方式来模拟 1.mysq由于默认是开启自动提交事务,所以首先得查看自己当前的数据库是否开启了自动提交事务. 命令:select @@autocommit; 结果如下: +--------------+ | @@autocommit | +--------------+ | 0 | +--------------+ 如果是1,那么运行命令:set autocommit = 0;设置为不开启自动提交 2.当前的数据库表格式如下 tb

  • MySQL执行事务的语法与流程详解

    摘要:MySQL 提供了多种存储引擎来支持事务. MySQL 提供了多种存储引擎来支持事务.支持事务的存储引擎有 InnoDB 和 BDB,其中,InnoDB 存储引擎事务主要通过 UNDO 日志和 REDO 日志实现,MyISAM 存储引擎不支持事务. 拓展:任何一种数据库,都会拥有各种各样的日志,用来记录数据库的运行情况.日常操作.错误信息等,MySQL 也不例外.例如,当用户 root 登录到 MySQL 服务器,就会在日志文件里记录该用户的登录时间.执行操作等. 为了维护 MySQL 服

  • mysql、oracle默认事务隔离级别的说明

    1.事务的特性(ACID) (1)原子性(Atomicity).事务中所涉及的程序对数据库的修改操作要么全部成功,要么全部失败. (2)一致性(Consistency).事务执行前和执行后来源和去向保持平衡. (3)隔离性(Isolation).并发时每个事务是隔离的,相互不影响. (4)持久性(Durubility).一旦事务成功提交,应该保证数据的完整存在. 2.事务隔离级别 (1)read uncommitted 未提交读 所有事务都可以看到没有提交事务的数据. (2)read commi

  • 详解MySQL中事务隔离级别的实现原理

    前言 说到数据库事务,大家脑子里一定很容易蹦出一堆事务的相关知识,如事务的ACID特性,隔离级别,解决的问题(脏读,不可重复读,幻读)等等,但是可能很少有人真正的清楚事务的这些特性又是怎么实现的,为什么要有四个隔离级别. 今天我们就先来聊聊MySQL中事务的隔离性的实现原理,后续还会继续出文章分析其他特性的实现原理. 当然MySQL博大精深,文章疏漏之处在所难免,欢迎批评指正. 说明 MySQL的事务实现逻辑是位于引擎层的,并且不是所有的引擎都支持事务的,下面的说明都是以InnoDB引擎为基准.

  • MySQL 查看事务和锁情况的常用语句分享

    一些查看数据库中事务和锁情况的常用语句 查看事务等待状况: SELECT r.trx_id waiting_trx_id, r.trx_mysql_thread_id waiting_thread, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_mysql_thread_id blocking_thread, b.trx_query blocking_query FROM information_schema.innodb_

  • MySQL如何实现事务的ACID

    前言 最近在面试,有被问到,MySQL的InnoDB引擎是如何实现事务的,又或者说是如何实现ACID这几个特性的,当时没有答好,所以自己总结出来,记录一下. 事务的四大特性ACID 事务的四大特性ACID分别是,A-原子性(Atomicity),C-一致性(Consistency),I-隔离性(Isolation),D-持久性(Durability).一致性是最终目的,原子性.隔离性.持久性是为了保证一致性所做的措施.所以我写的顺序并不是按照ACID来写的,将一致性放到了最后,顺序就变成了,AD

  • 深入理解PHP+Mysql分布式事务与解决方案

    事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元: 事务的ACID特性 事务应该具有4个属性:原子性.一致性.隔离性.持续性 原子性(atomicity).一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做. 一致性(consistency).事务必须是使数据库从一个一致性状态变到另一个一致性状态.一致性与原子性是密切相关的. 隔离性(isolation).一个事务的执行不能被其他事务干扰.即一个事务内部的操作及使用的数据对并发的其他事务是

随机推荐