MySQL日志系统详解
MySQL日志系统详解
从理解 SQL 执行过程到理解MySQL 日志系统的工作过程。
1. 一条 select 语句的执行过程
一条查询语句执行的过程,属于读一条记录的过程,大致可以分为以下几个步骤:
建立客户端/服务器通信:
- 客户端发起的连接请求,通过MySQL
连接器处理后,客户端将SQL查询语句发送到服务器。
- 客户端发起的连接请求,通过MySQL
查询解析:
- SQL解析器:
SQL解析器首先会对查询语句进行语法和词法分析,生成一个解析树(Parse Tree)。 - 预处理器:
预处理器进一步检查解析树的合法性,包括表和列是否存在、名称是否正确等。
- SQL解析器:
查询优化:
- 查询重写:
优化器可能会对解析树进行重写,例如将子查询转换为连接(JOIN),或者进行某些常见的SQL重写优化。 - 选择执行计划:
优化器会生成多个执行计划,并选择其中的最优计划。这里会考虑索引的使用、表扫描的方式(全表扫描或索引扫描)等。 - 成本估算:MySQL使用一种
基于成本的优化算法,通过估算不同执行计划的代价,选择成本最低的计划。
- 查询重写:
查询执行:
- 存储引擎接口:MySQL的
执行器根据优化器选择的执行计划,通过存储引擎接口调用具体的存储引擎(如InnoDB、MyISAM等)。 - 存储引擎操作:存储引擎根据执行器的请求进行数据的读取、写入等操作。
- 存储引擎接口:MySQL的
结果返回:
- 结果集处理:执行器将存储引擎返回的数据进行处理,生成最终的
结果集。 - 结果发送:最终的结果集通过网络传输返回给客户端。
- 结果集处理:执行器将存储引擎返回的数据进行处理,生成最终的
缓存处理(可选):
- 查询缓存:如果查询缓存开启且命中缓存,MySQL会直接从缓存中返回结果,而不经过上述大部分步骤。需要注意的是,MySQL 8.0 版本已移除查询缓存这一特性。
总结来说,MySQL一条查询语句的执行过程可以概括为:客户端发送SQL语句 -> 解析语句 -> 优化查询 -> 执行查询 -> 返回结果。每个步骤中都有许多细节和优化点,使得MySQL能够高效地处理各种查询请求。
引用小林图解的一张图: 
2. 一条 update 语句的执行过程
比如这条待执行的 update 语句:UPDATE t_user SET name = 'kryiea' WHERE id = 10086;
待执行的语句执行过程:
select语句的那一套流程,update语句也是同样会走一遍- 查询到目标记录后,执行更新操作的同时会涉及对三种日志的改动,
undolog、redolog、binlog。
3. 三种日志、MVCC、BufferPool 之间的相互配合
3.1 三种日志的主要作用
- UndoLog 回滚日志: 是Innodb存储引擎层生成的日志,保证了事务中的原子性,主要用于事务回滚和MVCC。(撤销已经执行的修改,保证事务的原子性和一致性)
- RedoLog 重做日志: 是Innodb存储引擎层生成的日志,保证了事务中的持久性,主要用于掉电等故障恢复。(重做已经提交的修改,保证事务的持久性)
- BinLog 归档日志: 是 Server层 生成的日志,主要用于数据备份和主从复制。(记录和重放SQL语句,用于数据的复制和恢复)
⬇️辅助理解: InnoDB 存储引擎的日志:
- UndoLog 记录了此次事务
开始前的数据状态,记录的是更新 之前 的值; - RedoLog 记录了此次事务
完成后的数据状态,记录的是更新 之后 的值;
Server 层的日志:
- BinLog 记录了完成一条更新操作后,Server 层还会生成一条
binlog,等之后事务提交的时候,会将该事物执行过程中产生的所有binlog统一写入binlog文件。
3.2 UndoLog 回滚日志
3.2.1 为什么需要 UndoLog
先了解隐式事务: Innodb 引擎在执行一条增删改语句的时候,即使没有显式输入begin开启事务和commit提交事务,也会自动隐式开启事务。 而且执行一条 update 语句是否自动提交事务,是由 autocommit 参数决定,默认开启。
试想以下场景: 在一个事务在执行过程中,在还没有提交事务之前,如果 MySQL 发生了崩溃,要怎么回滚到事务之前的数据呢?
如何解决: 如果每次在事务执行过程中,都记录下回滚时需要的信息到一个日志(undolog)里,那么在事务执行中途发生了 MySQL 崩溃后,就不用担心无法回滚到事务之前的数据,我们可以通过这个日志回滚到事务之前的数据。
3.2.2 认识 Undolog 机制
在事务没提交之前,MySQL 会先记录更新前的数据到 undolog日志文件,当事务需要回滚时,可以利用 undolog 来进行回滚。
过程如下图: 
3.2.3 Undolog 如何记录和回滚
每当 InnoDB 引擎对一条记录进行操作时,要把回滚时需要的信息都记录到 undolog 里,比如:
- 在
插入一条记录时,要把这条记录的主键值记下来,这样回滚时只需要把这个主键值对应的记录delete就好了; - 在
删除一条记录时,要把这条记录中的内容都记下来,这样回滚时再把由这些内容组成的记录insert到表中就好了; - 在
更新一条记录时,要把被更新的列的旧值记下来,这样回滚时再把这些列update为旧值就好了。
3.2.4 Undolog 日志的格式
需要了解一条记录在innodb引擎中的存储格式。
一条记录的每一次更新操作产生的 undolog 中,都有一个 roll_pointer 指针和一个 trx_id 事务id:
- 通过
trx_id可以知道该记录是被哪个事务修改的。 - 通过
roll_pointer指针可以将这些undolog串成一个链表,这个链表就被称为版本链。
版本链如下图:
3.2.5 Undolog + ReadView 实现 MVCC
MVCC - Multi-version concurrency control 多版本并发控制(MVCC)是一种数据库管理技术,通过维护数据的多个版本来实现并发访问,从而提高读写操作的性能和一致性。
并发访问的多个版本通过快照控制: 对于读提交和可重复读隔离级别的事务来说,它们的快照读(普通 select 语句)是通过 ReadView + undolog 来实现的。 读提交 和 可重复读 的 快照读 区别在于创建 ReadView 的时机不同:
读提交隔离级别:每次执行 select 都会生成一个新的 ReadView,也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。可重复读隔离级别:在启动事务时生成一个 ReadView,然后整个事务期间都在用这个 ReadView,这样就保证了在事务期间读到的数据都是事务启动前的记录。
如何知道版本的可见性,参考 3.2.6 : 通过 事务的 ReadView 里的字段 和 记录中的两个隐藏列 trx_id 和 roll_pointer 的比对,如果不满足可见行,就会顺着 undolog 版本链里找到满足其可见性的记录,从而控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)
3.2.6 ReadView 机制
ReadView 的四个字段:
ReadView 可以理解为记录当前事务id创建时,整个数据库还有哪些其他活着的事务,记录下来,以便于判断数据的可见性。
creator_trx_id:代表创建当前这个 ReadView 的事务ID。m_ids:表示在生成当前 ReadView 时,系统内活跃且未提交的事务ID列表。min_trx_id:活跃的事务列表中最小的事务ID。max_trx_id:表示在生成当前 ReadView 时,系统中要给下一个事务分配的ID值
如何判断可见性: 判定方法:事务 readview 里的字段 与 记录中的两个隐藏列 进行对比:
- 如果
事务ReadView 中的 min_trx_id 值>=记录的 trx_id 值,表示这个版本的记录是在创建 ReadView 前 已经提交的事务生成的,所以该版本的记录对当前事务 可见 。 - 如果
事务ReadView 中的 max_trx_id 值<=记录的 trx_id 值,表示这个版本的记录是在创建 ReadView 后 才启动的事务生成的,所以该版本的记录对当前事务 不可见 。 - 如果
事务ReadView 中的 min_trx_id<记录的 trx_id 值<事务ReadView 中的 max_trx_id,需要判断trx_id是否在m_ids列表中:- 如果
记录的 trx_id在m_ids列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务 不可见 。 - 如果
记录的 trx_id不在m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务 可见 。
- 如果
3.2.7 UndoLog 如何刷盘的
Undolog和数据页的刷盘策略是一样的,都需要通过 Redolog 保证持久化。Buffer pool中有Undo 页,对Undo 页的修改也都会记录到 Redolog。Redolog会每秒刷盘,提交事务时也会刷盘,数据页和Undo 页都是靠这个机制保证持久化的。
3.3 Buffer Pool 缓冲池
3.3.1 Buffer Pool 的意义
场景: MySQL 的数据都是存在磁盘中的,那么我们要更新一条记录的时候,得先要从磁盘读取该记录,然后在内存中修改这条记录。
那修改完这条记录是选择直接写回到磁盘,还是选择缓存起来呢: 这也是 Buffer Pool 的意义。 当然是缓存起来好,这样下次有查询语句命中了这条记录,直接读取缓存中的记录,就不需要从磁盘获取数据了。
有了Buffer Pool后:
- 当读取数据时,如果数据存在于
BufferPool中,客户端就会直接读取BufferPool中的数据,否则再去磁盘中读取。 - 当修改数据时,如果数据存在于
Buffe Pool中,那直接修改BufferPool中数据所在的页,然后将其页设置为脏页(该页的内存数据和磁盘上的数据已经不一致),为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。
Buffer Pool 属于哪一层: 属于:Innodb引擎层。 区别:不是本文开头讨论 select 语句执行过程中提到的缓存,那个是在 server 层的。
3.3.2 Buffer Pool 缓存什么
InnoDB中磁盘与内存的交互基本单位: InnoDB 会把存储的数据划分为若干个页,以页作为磁盘和内存交互的基本单位,一个页的默认大小为 16KB。因此,BufferPool 同样需要按页来划分,使用与存储引擎一样的基本单位。
在 MySQL 启动的时候: InnoDB 会为 BufferPool 申请一片连续的内存空间,然后按照默认的 16KB 的大小划分出一个个的页, BufferPool 中的页就叫做缓存页。 此时这些缓存页都是空闲的,之后随着程序的运行,会有磁盘上的页被加载缓存到 BufferPool 中的缓存页。
在 MySQL 启动完成的时候: 由于是先申请了一片连续的内存空间但没写入具体数据,所以可以观察到使用的虚拟内存空间很大,而使用到的物理内存空间却很小。 这是因为只有这些虚拟内存被访问后,操作系统才会触发缺页中断,申请物理内存,接着将虚拟地址和物理地址建立映射关系。
BufferPool 可以缓存的数据类型:
Undo 页是记录什么的 开启事务后,InnoDB 会在更新记录之前,先记录相应操作的 undolog 来保证事务的原子性。
比如:如果是 update 操作,需要把被更新的列的旧值记下来,旧值作为一条 undolog,然后把这条 undolog 写入 BufferPool 中的 Undo页
查询一条记录,只需要缓存一条记录吗? 刚刚提到了 InnoDB 存储引擎以16KB大小的页作为磁盘与内存交互的基本单位,所以查询一条记录的时候,会将整个页加载进 BufferPool 中,再通过页的页目录去定位到某条具体的记录.
上面提到的 页目录 是什么? 简单来说:页目录类似于字典的目录,用于快速定位某条记录的大致位置。
这个问题需要了解一个 页 内部是如何组织数据的。小林图解也有提到,这里附上一张更具体的图来辅助理解。
- 一个页空间会被划分成许多部分,有:
文件头、页头、最大最小记录、用户记录、空闲空间、文件尾等。 - 主要关注
用户记录:存储的一行行记录会被存放在这里,记录还会进一步被分成一个个组,每一个组内部都有一些数据记录。 - 再看到左边的
页目录:有一个个的槽位,其指向每一个分组内的最后一条记录。

3.3.3 Buffer Pool 刷盘策略
深入学习文章推荐:(十二)MySQL之内存篇:深入探寻数据库内存与Buffer Pool的奥妙! - 竹子爱熊猫
3.4 RedoLog 重做日志
3.4.1 为什么需要 Redolog
试想以下场景: Buffer Pool 是基于内存的,而内存总是不可靠,万一断电重启,还没来得及落盘的脏页数据就会丢失。
解决方案: 采用 WAL (Write-Ahead Logging) 技术。
- MySQL 的写操作并不是立刻写到磁盘上,而是先写日志文件,然后在合适的时间再将新的记录写到磁盘上。
- 在事务提交时,数据库系统只需确保 redolog 已经写入磁盘,而数据页可以稍后再写入。
解决方案的场景: 当有一条记录需要更新的时候,InnoDB 引擎会先更新 Buffer Pool 中的数据页,同时将该页标记为脏页。然后,将本次对这个页修改后的数据状态以 redo log 的形式记录下来并写入 redo log 文件,这时更新操作就算完成了。
到这里,只是完成了 redo log 文件的刷盘,但还未完成将最新的数据记录(脏的数据页)刷盘到存储表数据的文件 xxx.ibd 中。简而言之:日志文件是最新的,但数据库文件还不是最新的,还需要完成最后的 Buffer Pool --> 磁盘 操作,才能完成持久化存储。
后续,InnoDB 引擎会在适当的时候,由后台线程将缓存在 Buffer Pool 的脏页刷新到磁盘里,这就是 WAL 的核心思想。
图示,第4步的细节参考 3.4.5:
3.4.2 认识 Redolog 机制
redolog 是物理日志,记录了某个数据页做了什么修改
如何记录这个 "修改": 格式:AAA表空间 中的 BBB数据页 的 CCC偏移量 的地方做了 DDD的更新,每当执行一个事务就会产生这样的一条或者多条物理日志。
WAL: 在事务提交时,只要先将 redo log 持久化到磁盘即可,可以不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘。
如何保证事务的持久性: 当系统崩溃时,虽然脏页数据没有持久化,但是 redolog 已经持久化,MySQL 能在重启后根据 redolog 的日志内容,将所有数据恢复到最新的状态。
3.4.3 Redolog 与 Undolog 的配合
配合场景: 开启事务后,InnoDB 在进行更新操作之前,首先会记录相应的 undolog。
如果是更新操作,InnoDB 需要将被更新的列的旧值记下来,也就是生成一条 undolog。这个 undolog 会被写入 Buffer Pool 中的 Undo 页面。 然后在 BufferPool 中完成数据页的更新,标记该页为脏页,并且记录对于的 redolog。
具体的配合过程:
- 记录 undolog:
- 当事务对数据库中的记录进行更新时,InnoDB 会先生成一条
undolog,记录被修改前的数据。这个操作是为了保证在事务回滚时能够恢复到原始状态。 - 生成的 undolog 会被写入 BufferPool 中的
Undo 页面,并在内存中进行修改。
- 记录 redolog:
- 在内存中修改
Undo 页面后,InnoDB 还需要记录对应的redo log。redo log记录的是对数据页的物理修改操作,是用来在系统崩溃后进行数据恢复的。 - 具体来说,
redo log会记录对数据页的修改操作细节,包括何时修改、修改了哪些数据等。这些信息会先写入redolog buffer中,并在适当的时机(例如事务提交时)刷写到磁盘上的redo log文件中。
Redolog Buffer 与 Buffer Pool 不一样! Redolog Buffer 是 redolog 自己的缓存 具体细节往下看。
3.4.4 Redolog与数据分开写入磁盘的必要性
能提高数据库的写性能:
- 顺序写入:
redo log的写入是顺序写入,采用在文件尾部追加写入文件的方式,这样可以减少磁盘的寻道时间和旋转延迟,从而提高写入速度。- 顺序写入的性能通常比随机写入高
- 随机写入:
数据页的写入通常是随机写入。随机写入是指数据写入磁盘的不同位置,写入速度较慢。- 通过将
数据页首先写入缓冲池(Buffer Pool),然后在适当时机批量写入磁盘,可以减少随机写入的频率和次数。
3.4.5 Redolog 的刷盘策略
执行一个事务的过程中,产生的 redo log 也不是直接写入磁盘的,因为这样会产生大量的 I/O 操作,而且磁盘的运行速度远慢于内存。 所以,redo log 也有自己的缓存 redo log buffer ,每当产生一条 redo log 时,会先写入到 redo log buffer,后续再持久化到磁盘.
redo log buffer 默认大小 16 MB,可以通过 innodb_log_Buffer_size 参数动态的调整大小,增大它的大小可以让 MySQL 处理大事务时不必写入磁盘(提高了写入磁盘的阈值),进而提升写 IO 性能。
redo log buffer 的持久化如下图: 
3.4.6 redolog buffer 的刷盘时机:
主要的几个刷盘时机:
- MySQL 正常关闭时。
- 当
redo log buffer中记录的写入量大于 redo log buffer 内存空间的一半时,会触发落盘。 - InnoDB 的
后台线程每隔 1 秒,将redo log buffer持久化到磁盘。 每次事务提交时都将缓存在redo log buffer里的redo log直接持久化到磁盘(这个策略可由innodb_flush_log_at_trx_commit参数控制)。
了解一个主要参数 innodb_flush_log_at_trx_commit:
innodb_flush_log_at_trx_commit = 0每次事务提交时 ,还是将redo log留在redo log buffer中 ,该模式下在事务提交时不会主动触发写入磁盘的操作。innodb_flush_log_at_trx_commit = 1这是默认值。 每次事务提交时,都 将缓存在redo log buffer里的redo log直接持久化到磁盘 ,这样可以保证 MySQL 异常重启之后数据不会丢失。innodb_flush_log_at_trx_commit = 2每次事务提交时,都只是缓存在redo log buffer里的redo log写到redo log 文件,注意并不意味着写入到了磁盘 ,因为操作系统的文件系统中有个Page Cache(Page Cache 是专门用来缓存文件数据的,所以写入redo log文件意味着写入到了操作系统的文件缓存。
3.4.7 Redolog 日志重写
问题背景: redolog 文件写满了/文件过大怎么办?
解决方案 - 日志重写: 默认情况下,InnoDB 存储引擎有 1 个重做日志文件组 redo log Group,由 2 个 redolog 文件 组成,分别是:ib_logfile0 和 ib_logfile1。
日志重写方式:循环写重做日志文件组是以 循环写 的方式工作的,从头开始写,写到末尾就又回到开头,相当于一个环形。 先写 ib_logfile0 文件,当 ib_logfile0 文件被写满的时候,会切换至 ib_logfile1 文件,当 ib_logfile1 文件也被写满时,会切换回 ib_logfile0 文件。
图示: 
3.5 BinLog 重做日志
3.5.1 Binlog 的作用
- binlog 是 MySQL 的 Server 层实现的日志,所有存储引擎都可以使用,用于备份恢复、主从复制等;
- binlog 文件是记录了
所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作,比如SELECT和SHOW操作。 - binlog 是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志。
产生 binlog 的场景: MySQL 在完成一条更新操作后,Server 层会生成一条 binlog,将其写到 binlog cache(Server 层的 cache),等之后事务提交的时候,会将该事务执行过程中产生的所有 binlog 统一写入 binlog 文件。
3.5.2 Binlog 刷盘策略
MySQL 给每一个处理线程分配了一片内存用于缓冲 binlog,该内存叫 binlog cache。
事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。
关键点1: 一个事务的 binlog 是不能被拆开的,因此无论这个事务有多大(比如有很多条语句),也要保证一次性写入。
MySQL 设定一个处理线程只能同时有一个事务在执行,所以每当执行一个 begin/start transaction 的时候,就会默认提交上一个事务。
关键点2: 场景:什么时候 binlog cache 会写到 binlog 文件?
回答:在事务提交的时候,执行器把 binlog cache 里的完整事务写入到 binlog 文件中,并清空 binlog cache。
关键点3: 每一个线程都有自己的binlog cache,最终写入同一个Binlog文件
关键点4: 场景:如关键点3所提到的 最终写入同一个Binlog文件,那这里的并发问题如何解决?
回答:MySQL采用了多种机制来确保并发安全和一致性
- 锁机制
- 顺序写入
- 组提交 Group Commit
关键点5: 场景:写入binlog文件的过程还可以继续拆分。
- 系统调用
write()后,会先写入内核的缓冲区page cache,这里不涉及磁盘I/O - 内核再通过
fsync()持久化到磁盘,这里涉及磁盘I/O。频繁的fsync()会导致磁盘的I/O升高。

关键点6:fsync() 的频率由参数 sync_binlog 控制
sync_binlog = 0是默认值 表示每次提交事务都只write,不fsync,后续交由操作系统决定何时将数据持久化到磁盘。sync_binlog = 1表示每次提交事务都会write,然后马上执行fsync,最多丢失一个事务的binlog。sync_binlog > 1表示每次提交事务都write,但累积N个事务后才fsync。
3.5.3 Binlog - 主从同步模型
主从同步过程:
- 写入 binlog: 主库修改数据后,写入
binlog 日志,提交事务,更新本地存储的数据。 - 同步 binlog: 从库连接到主库后,主库会创建一个
dump 线程,把binlog同步到所有从库,每个从库把binlog写到暂存日志中。 - 回放 binlog: 从库启动一个
sql 线程去回放binlog,去读relay log 中继日志然后回放binlog更新数据。
三种主从同步模式: MySQL 默认的同步模式:异步模式
- 同步模式: 主库提交事务的线程要等待所有从库的同步成功,才返回客户端结果。性能最差了。
- 异步模式: 主库提交事务的线程不会等待 binlog 同步完成就返回客户端结果,性能最好,但是主库宕机,数据就会丢失。
- 半同步模式: 比如一主二从的集群,只要成功同步到一个从库,就立即返回数据给客户端。即使主库宕机,仍有一个从库有最新数据。
3.6 两阶段提交
3.6.1 两阶段提交的提出
思考以下问题: 事务提交后,redo log 和 binlog 都要持久化到磁盘,但是这两个是独立的逻辑,可能出现半成功的状态,这样就造成两份日志之间的逻辑不一致。
问题的场景复现:
原数据:表名 t_user,某行记录 id = 1;name = jay 执行SQL:UPDATE t_user SET name = 'kryiea' WHERE id = 1 事务提交后:进行持久化 redolog和 binlog。
这两个日志的刷盘先后顺序可能会导致下面两种情况:
- 如果在将
redo log刷入到磁盘之后, MySQL 突然宕机了,而binlog还没有来得及写入。- MySQL 重启后,通过
redo log能将Buffer Pool中id = 1这行数据的name字段恢复到新值kryiea。 - 但是
binlog里面没有记录这条更新语句,在主从架构中,binlog会被复制到从库,由于binlog丢失了这条更新语句,从库的这一行name字段是旧值jay,与主库的值不一致。
- MySQL 重启后,通过
- 如果在将
binlog刷入到磁盘之后, MySQL 突然宕机了,而redo log还没有来得及写入 。- 由于
redo log还没写,崩溃恢复以后这个事务无效,所以id = 1这行数据的name字段还是旧值jay。 - 而
binlog里面记录了这条更新语句,在主从架构中,binlog会被复制到从库,从库执行了这条更新语句,那么这一行name字段是新值kryiea,与主库的值不一致。
- 由于
问题的解决方案: 两阶段提交
3.6.2 两阶段提交的概念
两阶段提交其实是分布式事务一致性协议,它可以保证多个逻辑操作要不全部成功,要不全部失败,不会出现半成功的状态。
两阶段提交把单个事务的提交拆分成了 2 个阶段,分别是准备(Prepare)阶段和提交(Commit)阶段 ,每个阶段都由协调者(Coordinator)和参与者(Participant)共同完成。
协调者与参与者之间的协作: 例子来自小林图解。
举个拳击比赛的例子,两位拳击手(参与者)开始比赛之前,裁判(协调者)会在中间确认两位拳击手的状态,类似于问你准备好了吗?
- 准备阶段 : 裁判(协调者)会依次询问两位拳击手(参与者)是否准备好了,然后拳击手听到后做出应答,如果觉得自己准备好了,就会跟裁判说准备好了;如果没有自己还没有准备好(比如拳套还没有带好),就会跟裁判说还没准备好。
- 提交阶段 : 如果两位拳击手(参与者)都回答准备好了,裁判(协调者)宣布比赛正式开始,两位拳击手就可以直接开打;如果任何一位拳击手(参与者)回答没有准备好,裁判(协调者)会宣布比赛暂停,对应事务中的回滚操作。
3.6.3 两阶段提交的过程
在 MySQL 的 InnoDB 存储引擎中,开启 binlog 的情况下,MySQL 会同时维护 binlog与 redolog,为了保证这两个日志的一致性,MySQL 事务提交时使用 内部 XA 事务 来保证一致性。
内部 XA 事务由 binlog 作为协调者,存储引擎是参与者。
(是的,也有外部 XA 事务)
当客户端执行 commit 语句或者在自动提交的情况下,MySQL 内部开启一个 XA 事务, 分两阶段来完成 XA 事务的提交 。
场景举例: 事务的提交过程有两个阶段:将 redolog 的写入拆成了两个步骤 prepare 和 commit,中间再穿插写入 binlog。
具体如下:
prepare 阶段:将XID(内部 XA 事务的 ID)写入到redo log,同时将redo log对应的事务状态设置为prepare,然后将redo log持久化到磁盘(innodb_flush_log_at_trx_commit = 1的作用)commit 阶段:把XID写入到binlog,然后将binlog持久化到磁盘(sync_binlog = 1的作用),接着调用引擎的提交事务接口,将redo log状态设置为commit,此时该状态并不需要持久化到磁盘,只需要write到文件系统的page cache中就够了。因为只要binlog写磁盘成功,就算redo log的状态还是prepare也没有关系,一样会被认为事务已经执行成功;

3.6.4 两阶段提交有什么问题
两阶段提交虽然保证了两个日志文件的数据一致性,但是性能很差。
主要有两个方面的影响:
- 磁盘 I/O 次数高 : 对于"双1"配置,每个事务提交都会进行两次
fsync(刷盘),一次是redo log 刷盘,另一次是binlog 刷盘。 - 锁竞争激烈 : 两阶段提交虽然能够保证
单事务两个日志的内容一致,但在多事务的情况下,却不能保证两者的提交顺序一致,因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性,从而保证多事务的情况下,两个日志的提交顺序一致。
3.6.5 对两阶段提交加强:加入组提交
MySQL 引入了组提交(group commit)机制,当有多个事务提交的时候,会将多个 binlog 刷盘操作合并成1个,从而减少磁盘 I/O 的次数。
引入了组提交机制后,prepare 阶段不变,只针对 commit 阶段,将 commit 阶段拆分为3个过程: ● flush 阶段 : 多个事务按进入的顺序将 binlog 从 cache 写入文件(不刷盘); ● sync 阶段 : 对 binlog 文件做 fsync 操作(多个事务的 binlog 合并一次刷盘); ● commit 阶段 : 各个事务按顺序做 InnoDB commit 操作;
