2026-07-31 21:22:31
MVCC 是通过「版本链」来控制并发事务访问同一个记录时的行为。它主要通过隐藏字段、undo log、ReadView 来实现。
隐藏字段:
DB_TRX_ID(transaction_id):事务id,占6字节。当一个事务对某条记录进行改动时,就会将该事务的事务id记录在trx_id隐藏列里。
DB_ROLL_PTR(roll_pointer):回滚指针,占7字节。每次对某条记录进行改动时,都会把旧版本的记录写入到undo日志中,然后这个隐藏列是指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。
Undo Log:MySQL对数据进行修改操作时,并不是直接覆盖数据,而是通过undo log形成一个版本链。
ReadView:ReadView相当于数据快照。在「读已提交」隔离级别下,每个语句执行前都会重新生成一个ReadView;而在「可重复读」隔离级别下,是启动事务时生成一个ReadView,然后整个事务期间都在用这个ReadView。
ReadView有四个字段:
m_ids:指的是在创建ReadView时,当前数据库中「活跃事务」的事务id列表,「活跃事务」指的就是启动了但还没提交的事务。
min_trx_id:指的是在创建ReadView时,当前数据库中「活跃事务」中事务id最小的事务,也就是m_ids的最小值。
max_trx_id:这个并不是m_ids的最大值,而是创建ReadView时当前数据库中应该给下一个事务的id值,也就是全局事务中最大的事务id值+1。
creator_trx_id:指的是创建该ReadView的事务的事务id。
一个事务去访问记录时,除了自己的更新记录总是可见之外,还有以下情况:
如果记录的trx_id值小于ReadView中的min_trx_id值,表示这个版本的记录是在创建ReadView前已经提交的事务生成的,所以该版本的记录对当前事务可见。
如果记录的trx_id值大于等于ReadView中的max_trx_id值,表示这个版本的记录是在创建ReadView后才启动的事务生成的,所以该版本的记录对当前事务不可见。
如果记录的trx_id值在ReadView的min_trx_id和max_trx_id之间,需要判断trx_id是否在m_ids列表中:
如果记录的trx_id在m_ids列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。
如果记录的trx_id不在m_ids列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。

在MySQL数据库中,有三种重要的日志:Undo Log(回滚日志)、Redo Log(重做日志)和Bin Log(二进制日志)。它们在数据库事务的持久性、恢复和复制等方面起着关键作用。
Undo Log(回滚日志):
用于事务的回滚操作。当一个事务执行更新操作时,会生成Undo Log记录旧数据的备份,以便在事务回滚时还原数据到原始状态。这样即使事务发生错误或回滚,数据库也能保持一致性。
Undo Log通常存储在内存中,但如果内存不足,也可以存储在磁盘上。
用于保证事务的原子性和实现MVCC。
Redo Log(重做日志):
用于事务的持久性和恢复。当事务执行更新操作时,会先将修改记录到Redo Log中,然后再更新数据页。这样即使在事务提交之前数据库崩溃,也可以通过Redo Log来恢复数据,保证数据的一致性。
Redo Log通常存储在磁盘上。
保证了事务的持久性。
Bin Log(二进制日志):
是MySQL的复制和恢复机制的关键组成部分。它记录了数据库中的所有更改操作,包括数据的插入、更新和删除。
Bin Log可以用于数据库的备份和恢复,也可以用于实现主从复制。通过读取Bin Log,可以将主数据库的更改操作复制到从数据库,从而实现数据的同步。
Bin Log存储在磁盘上。
这些日志在MySQL数据库中相互配合,确保了事务的一致性和持久性,同时也提供了数据的恢复和复制功能。
3、主从架构的数据同步方式3.1 两阶段提交事务提交后,redo log和binlog都要持久化到磁盘,但是这两个是独立的逻辑,可能出现半成功的状态,这样就造成两份日志之间的逻辑不一致。
在持久化redo log和binlog这两份日志的时候,如果出现半成功的状态,就会造成主从环境的数据不一致性。因为redo log影响主库的数据,binlog影响从库的数据,所以redo log和binlog必须保持一致才能保证主从数据一致。
MySQL为了避免出现两份日志之间的逻辑不一致的问题,使用了「两阶段提交」来解决。两阶段提交其实是分布式事务一致性协议,它可以保证多个逻辑操作要不全部成功,要不全部失败,不会出现半成功的状态。
当客户端执行commit语句或者在自动提交的情况下,MySQL内部开启一个XA事务,分两阶段来完成XA事务的提交,分别是准备阶段和提交阶段。
不管是redo log已经写入磁盘,binlog还没写入磁盘,还是redo log和binlog都已经写入磁盘,还没写入commit标识时崩溃,此时的redo log都处于prepare状态。
在MySQL重启后会按顺序扫描redo log文件,碰到处于prepare状态的redo log,就拿着redo log中的XID去binlog查看是否存在此XID:
可以看到,对于处于prepare阶段的redo log,即可以提交事务,也可以回滚事务,这取决于是否能在binlog中查找到与redo log相同的XID,如果有就提交事务,如果没有就回滚事务。这样就可以保证redo log和binlog这两份日志的一致性了。
两阶段提交是以binlog写成功为事务提交成功的标识。事务没提交的时候,redo log也是可能被持久化到磁盘。但是binlog必须在事务提交之后,才可以持久化到磁盘。
3.2 组提交两阶段提交虽然保证了两个日志文件的数据一致性,但是性能很差,主要有两个方面的影响:
MySQL引入了binlog组提交(group commit)机制,当有多个事务提交的时候,会将多个binlog刷盘操作合并成一个,从而减少磁盘I/O的次数。MySQL 5.7开始有redo log组提交。
引入了组提交机制后,prepare阶段不变,只针对commit阶段,将commit阶段拆分为三个过程:
上面的每个阶段都有一个队列,每个阶段有锁进行保护,因此保证了事务写入的顺序,第一个进入队列的事务会成为leader,领导所在队列的所有事务,全权负责整队的操作,完成后通知队内其他事务操作结束。