MySQL究竟是如何做到持久性的?

article/2025/8/23 22:04:16

前言

我们学习事务中,对于持久性(durability)是这样定义的:事务一旦提交,则其所有的修改将会保存到数据库当做。即使此时系统崩溃,修改的数据也不会丢失。同时数据库连接中,默认有一个参数autocommit=1表示每次执行一条sql如果没有显示启动事务语句(begin或start transaction)就会隐试的开启一个事务。

所以通常情况下,我们对数据库做的任何修改,只要事务提交都可以确保数据不会丢失。

在MySQL中完美的支持事务的存储引擎只有InnoDB,所以以下所有内容都是在InnoDB存储引擎下的故不会再做特别声明。

事务的持久性究竟是如何实现的,下面我们将一点一点的来探讨。

日志系统

如果要解释清楚持久性,就绕不开日志系统。同时MySQL的日志系统非常重要,如果不理解日志系统,后面所有的东西将无法理解,所以我将会尽我所能来解释清楚这个东西。

redo log

我们知道数据是存储在磁盘当中的,如果每一次数据修改操作都要写进磁盘,然后磁盘找到对应的那一条记录,然后再去更新。整个过程看下来IO成本、查询成本都非常高。

为了解决这个问题,MySQL采用了一种叫WAL(Write Ahead Logging)提前写日志的技术。意思就是说,发生了数据修改操作先写日志记录下来,等不忙的时候再持久化到磁盘。这里提到的日志就是redo log。

redo log称为重做日志,当有一条记录需要修改的时候,InnoDB引擎会先把这条记录写到redo log里面。redo log是物理格式日志,它记录的是对于每个页的修改。

redo log是由两部分组成的:一是内存中的重做日志缓冲(redo log buffer);二是用来持久化的重做日志文件(redo log file)。所以为了消耗不必要的IO操作,事务再执行过程中产生的redo log首先会redo log buffer中,之后再统一存入redo log file刷盘进行持久化,这个动作成为fsync。

至于什么时候从redo log buffer写入redo log file,可以通过InnoDB提供的innodb_flush_log_at_trx_commit参数来配置。

  1. 设置为0的时候,表示事物提交的时候不写入重做日志文件持久化。
  2. 设置为1的时候,表示每次事务提交都将redo log直接持久化到磁盘
  3. 设置为2的时候,表示每次事务提交时将重做日志写入重做日志文件,但是写入的仅仅是文件系统的缓存page cache不进行fsync。

InnoDB有一个后台线程master thread,每隔一秒就会把redo log buffer中的日志文件调用write写到文件系统缓存page cache,然后调用fsync持久化磁盘。

虽然设置成0或者2可以提升效率,但是也丧失了事务持久性的特性。

  1. 如果设置为0,事务提交之后master thread还没有来得及持久化MySQL就宕机了,那么这部分数据将会丢失。
  2. 如果设置成为2,MySQL发生宕机并不会导致数据丢失,但是当操作系统宕机时,重启数据库将会丢失文件系统缓存page cache中那部分数据。

redo log file也并不是无限大,而是固定大小的,默认是2个一组在我们MySQL安装路径下面就会找到两个文件“ib_logfile0”和“ib_logfile1”。它是从头开始写,写到末尾后回到头开始循环写,如下图所示。

 

withe pos表示当前位置坐标,一边写一边往后移动。当write pos写到ib_logfile2末尾的时候,就会回到最开始的ib_logfile0文件。

check point表示已经刷新到磁盘上的位置,write pos到check point之间的位置表示安全可写的位置。如果write pos快要追上check point了,那么此时就暂停工作将一部分数据刷回磁盘。

Binlog

从MySQL架构上来看,主要分为Server层和存储引擎层。我们上面提到的redo log它是InnoDB引擎特有的日志,而Server层也有自己的日志,成为binlog二进制日志用于归档。

binlog记录了mysql执行更改了所有操作,但不包含select和show这类本对数据本身没有更改的操作。但是不是说对数据本身没有修改就不会记录binlog日志。

例如update t set a = 1 where a = 2,这条语句对数据库没有做任何修改,但是通过命令show binlog event也可以看到在二进制日志中做了记录。

Binlog日志的作用

  1. 恢复(recover):数据恢复
  2. 复制(replication):和恢复类似,用做主从复制

你可能会好奇,不是有了redo log还要再来一个binlog?

因为MySQL是存储引擎自带的,而redo log是InnoDB特有的。最开始的时候MySQL里没有InnoDB引擎,自带的MyISAM又没有crash-safe能力,binlog日志只能用于归档。为了让MySQL具有crash-safe能力所以就引入InnoDB,而InnoDB的crash-safe能力依靠redo log,所以就有了两套日志。

Redo log和binlog都是记录事务日志,他们有什么区别?

  1. binlog是mysql自带的,他会记录所有存储引擎的日志文件。而redo log是InnoDB特有的,他只记录改存储引擎产生的日志文件
  2. 记录内容不同:binlog是逻辑日志,记录这个语句具体操作了什么内容。Redo log是屋里日志,记录的是每个页的更改情况。
  3. 写入方式不同:redo log是循环写,只有那么大的空间。binlog采用追加写入,当一个binlog文件写到一定大小后会切换到下一个文件。

更新一条语句的流程

InnoDB存储引擎中一条简单的更新语句就如下

 

  1. 首先执行器调用引擎获取数据,如果数据在内存中就直接返回;否则先从磁盘中读取数据,写入内存后再返回。
  2. 修改数据后再调用引擎接口写入这行数据
  3. 引擎层将这行数据更新到内存中,然后将更新操作写入redo log,这时候redo log标记为prepare状态。然后告诉执行器我处理完了,可以提交事务了。
  4. 执行器生成这个操作的binlog,并把binlog写入磁盘,然后调用引擎提交事务
  5. 引擎收到commit命令后,把刚才写入的redo log改成commit状态

至此我们的一条更新语句就算基本完成了,这里面涉及了两阶段提交prepare阶段和commit阶段。

为什么需要两阶段提交?

之前我们也清楚了,binlog是MySQL中Server层的日志,redo log是InnoDB存储引擎特有的。我们为了一致性就需要把这两个日志很好的持久化下来。而上面的redo log经历prepare和commit两个阶段才算提交。要解释为redo log什么需要两阶段提交,那么我们就用反证法说一说如果没有两阶段提交会发生什么问题吧?

redo log然后写binlog

如果redo log写完后,写binlog的时候,MySQL进程异常重启。但是我们redo log写入了这条更新,所以重启后这条数据依然会被修改。

但是因为binlog还没有写完就崩溃了,当我们需要用binlog来恢复临时库的时候就发现少了一次更新,这时候就会发生不一致。

先写binlog然后写redo log

如果binlog写完之后,redo log还没有写完就异常重启。那么InnoDB引擎就会判断事物无效,回滚这次操作。但是binlog里面却记录了这次操作。当我们使用binlog来恢复的时候就又多了一个事务,这时候又会数据不一致。

两阶段提交中发送异常重启如何解决?

我们了解到了为什么需要两阶段提交,接下来我们分析一下两阶段提交中发生异常需要怎么处理的情况。

 

我们把两阶段提交过程中会发生问题的两个时刻分别标记为时刻A和时刻B,分别来分析这两个时刻发生故障会出现的问题。

时刻A发生故障

这时候发生故障的话,redo log处于prepare阶段,此时redo log还没有提交所以崩溃恢复的时候这个事务就会回滚本次提交。因为binlog还没有写,所以恢复数据的时候也不会执行此次事务

时刻B发生了故障

如果时刻B说明redo log处于prepare阶段,并且binlog已经写完了。但是执行器调用存储引擎提交事务时候,redo log还没来记得修改为commit的时候发生崩溃。那么此时就需要分情况。

重启过后InnoDB引擎发现redo log是prepare阶段,那么就会根据自己的XID去寻找对应的binlog(XID是他们共同的数据字段)。如果binlog是完整的,这时候极有可能binlog已经被备库同步了,那么此时直接提交事务。如果binlog不是完整的,那么此时回滚事务。

看到这里不知道你会不会有一个疑惑。处于prepare状态的redo log和完整的binlog就可以崩溃恢复了,为什么还要再设计redo log的commit阶段?

我们继续用反证法,如果没有commit阶段的话。当我们写入redo log后事务提交,然后写binlog的时候写入失败发生了崩溃。但是事务已经提交了,就不能回滚了。如果允许回滚的话,提交事务和写binlog这之间其他事务也对这条记录进行修改,回滚就会覆盖其他事务的更新。


http://chatgpt.dhexx.cn/article/lD4cVZsw.shtml

相关文章

理解事务四大特性(Transaction)——原子性、一致性、隔离性和持久性(ACID)

事务是指对系统进行的一组操作,为了保证系统的完整性,事务需要具有ACID特性,具体如下: 1. 原子性(Atomic) 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都…

持久性连接和非持久性连接

HTTP连接有两种,一种为持久性连接;另一种为非持久性连接。 由于不同的HTTP版本,使用不同的方式。 在这里分析一下二者的区别: 一、非持久性连接(Nonpersistent HTTP) 特点:每个TCP连接最多允许传输一个对象 HTTP 1.0使用的非持久性连接 过程: 响应时间分析与建…

mysql事务如何保证持久性_详解MySQL事务持久性实现

所谓MySQL事务持久性就是事务一旦提交,就是永久性的,不会因为宕机等故障导致数据丢失(外力影响不保证,比如磁盘损害)。持久性是保证了MySQL数据库的高可靠性(High Reliability),而不是高可用性(Hign Availability)。 MySQL的innoDB存储引擎,使用Redo log保证了事务的持久性…

Mysql持久性的实现

1、持久性的定义 事务一旦提交,则其所有的修改将会保存到数据库当中。即使此时系统崩溃,修改的数据也不会丢失。同时数据库连接中,默认有一个参数autocommit1(如果想要关掉,要set autocommit0,然后要手动的开启关闭&a…

数据库事务-持久性原理

持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。(持久性由redo log日志来保证) 以一个跟新语句执行流为例: 在存储引擎执行时,会先在缓存池…

MYSQL 1251

今天折腾mysql,,一直在连接的时候出现1251的报错,然后百度了很多方法,都没有办法成功,,最后折腾了好久,,终于成功了,进入MySQL 8.0 Command Line Clien,依次输…

java_1125

1。从键盘输入一个字符串 编写一个程序,判断输出一个字符串中大写英文字母数,和小写英文字母数,和其他非英文字母数 2. 编写一个方法,返回一个double类型的二维数组,数组中的元素通过解析字符串参数获得&#xff0c…

java_1115

定义一个接口 MediaPlayer,表示家庭影院的一个设备。MediaPlayer 中 包含 play(),stop(), open()三个方法,分别表示播放、停止和开仓功能。 MediaPlayer 有三个实现类,分别为: DVDPlayer,表示 …

java--Integer的128陷阱

包装类 提到128陷阱就不得先说一下包装类 1.为什么有包装类 在面向对象中,“一切皆对象”,但基本数据类型的数据不太符合这一理念,基本数据类型不是对象.涉及到类型之间的转化,数据类型之间的基本操作;如果都有我们…

P1152 java

package suanfa_xiaoqiang1; import java.util.Arrays; import java.util.Scanner; public class P1152 { public static void main(String[] args) { Scanner sc new Scanner(System.in); int nsc.nextInt(); int[] anew int[n1]; //数组遍历从1开始的时候,要加…

Java(11)

学习来源:日撸 Java 三百行(21-30天,树与二叉树) 第 28 天: Huffman 编码 (节点定义与文件读取) 输入:输入表示文本文件的字符串paraFilename 输出:构造对象tempHuffman并输出文本文件的内容inputText 优…

Java-1110

https://github.com/Lannister-never-pay/JavaWebLearning/tree/main/java1108 因为懒&#xff0c;还是用的1108的module JSP 指令 作用&#xff1a;用于配置JSP页面&#xff0c;导入资源文件 格式&#xff1a;<% 指令名称 属性名1属性值1 属性名2属性值2 %> 多个键值…

Java——详解Integer128陷阱

今天我们来一起探讨一下Java的128陷阱 首先我们通过代码对128陷阱进行一个认知 public static void main(String[] args){Integer a 127 ;Integer b 127 ;Integer c 128 ;Integer d 128 ;Integer e 1000 ;Integer f 1000 ;int a1 127;int b1 127;int c1 128;int d1 …

Java-11

学习来源&#xff1a;日撸 Java 三百行&#xff08;31-40天&#xff0c;图&#xff09;_闵帆的博客-CSDN博客 36 邻接表 36.1 相当于图的压缩存储. 每一行数据用一个单链表存储。 36.2 重写了广度优先遍历. 可以发现, 使用队列的机制不变. 仅仅是把其中的 for 循环换成了 wh…

JAVA101-135

JAVA101-150 字符串StringBuilder链式编程简化代码对应的关系可以使用查表法&#xff0c;通过数组的对应的下表来改变成相应的值 修改字符串字符串变整数重点&#xff1a;字符串变为数组 ArrayList集合的基本使用集合一开始的长度为0&#xff0c;如果用循环&#xff0c;进不去 …

Java-1214

Spring5总体学习内容 Spring基本概念IOC容器AopJdbcTemplate事务管理Spring5新特性 框架概述 Spring是轻量级的开源的JavaEE框架Spring可以解决企业应用开发的复杂性Spring有两个核心部分&#xff1a;IOC、Aop IOC&#xff1a;控制反转&#xff0c;把创建对象的过程交给Spri…

下载Google Play外国区APP技巧

安卓用户若遇到喜欢的APP是外国区的&#xff0c;只要翻墙就能下载。比起果粉还要注册&#xff0c;是简便很多。但有没有更简单的办法&#xff1f;这个必须有&#xff01;笔者前几天在网上闲逛时&#xff0c;就发现了一个给力的网站。让你不用翻墙&#xff0c;只需3个步骤&#…

Google Play国内应用市场发布版本步骤指导

应用发布步骤指导 前言Google Play华为小米Vivooppo 博客创建时间&#xff1a;2022.08.19 博客更新时间&#xff1a;2022.08.22 以Android studio build7.0.0&#xff0c;SDKVersion 31来分析讲解。如图文和网上其他资料不一致&#xff0c;可能是别的资料版本较低而已。 前言 …

Google Play App Signing的问题以及解决方式

Google Play App Signing是Google Play 的应用签名&#xff0c;在Google Play上创建项目的时候如果勾选了它&#xff0c;那么它就会生成一个签名文件&#xff0c;不管你上传到Google Play的apk是否用你的签名文件打包&#xff0c;最终都会被替换成Google Play App Signing里的签…

如何将Flutter开发的Android app 发布Google Play(谷歌应用商店)流程

将Flutter Android app 发布Google Play&#xff08;谷歌应用商店&#xff09;流程 一、首先就是要做到科学&#xff01; 二、打开google play官网&#xff0c;注册谷歌账号 三、打开谷歌开发者站点https://play.google.com/apps/publish/signup/创建你的App应用 四、创建完…