Mysql高级篇

大神,哪位知道,Mysql高级篇
最新回答
献世佛

2026-07-31 21:22:31

Mysql高级篇1、MVCC(多版本并发控制)

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列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。

2、三种日志:undo log、redo log和bin log

在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刷入到磁盘之后,MySQL突然宕机了,而binlog还没有来得及写入。MySQL重启后,通过redo log能将Buffer Pool中的相应数据恢复到新值,但是binlog里面没有记录这条更新语句,在主从架构中,binlog会被复制到从库,由于binlog丢失了这条更新语句,从库的数据是旧值。
  • 如果在将binlog刷入到磁盘之后,MySQL突然宕机了,而redo log还没有来得及写入。由于redo log还没写,崩溃恢复以后这个事务无效,所以数据还是旧值;而binlog里面记录了这条更新语句,数据是新值。

在持久化redo log和binlog这两份日志的时候,如果出现半成功的状态,就会造成主从环境的数据不一致性。因为redo log影响主库的数据,binlog影响从库的数据,所以redo log和binlog必须保持一致才能保证主从数据一致。

MySQL为了避免出现两份日志之间的逻辑不一致的问题,使用了「两阶段提交」来解决。两阶段提交其实是分布式事务一致性协议,它可以保证多个逻辑操作要不全部成功,要不全部失败,不会出现半成功的状态。

当客户端执行commit语句或者在自动提交的情况下,MySQL内部开启一个XA事务,分两阶段来完成XA事务的提交,分别是准备阶段和提交阶段。

  • 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也没有关系,一样会被认为事务已经执行成功。

不管是redo log已经写入磁盘,binlog还没写入磁盘,还是redo log和binlog都已经写入磁盘,还没写入commit标识时崩溃,此时的redo log都处于prepare状态。

在MySQL重启后会按顺序扫描redo log文件,碰到处于prepare状态的redo log,就拿着redo log中的XID去binlog查看是否存在此XID:

  • 如果binlog中没有当前内部XA事务的XID,说明redolog完成刷盘,但是binlog还没有刷盘,则回滚事务。
  • 如果binlog中有当前内部XA事务的XID,说明redolog和binlog都已经完成了刷盘,则提交事务。

可以看到,对于处于prepare阶段的redo log,即可以提交事务,也可以回滚事务,这取决于是否能在binlog中查找到与redo log相同的XID,如果有就提交事务,如果没有就回滚事务。这样就可以保证redo log和binlog这两份日志的一致性了。

两阶段提交是以binlog写成功为事务提交成功的标识。事务没提交的时候,redo log也是可能被持久化到磁盘。但是binlog必须在事务提交之后,才可以持久化到磁盘。

3.2 组提交

两阶段提交虽然保证了两个日志文件的数据一致性,但是性能很差,主要有两个方面的影响:

  • 磁盘I/O次数高:对于“双1”配置,每个事务提交都会进行两次fsync(刷盘),一次是redo log刷盘,另一次是binlog刷盘。
  • 锁竞争激烈:两阶段提交虽然能够保证「单事务」两个日志的内容一致,但在「多事务」的情况下,却不能保证两者的提交顺序一致,因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性,从而保证多事务的情况下,两个日志的提交顺序一致。

MySQL引入了binlog组提交(group commit)机制,当有多个事务提交的时候,会将多个binlog刷盘操作合并成一个,从而减少磁盘I/O的次数。MySQL 5.7开始有redo log组提交。

引入了组提交机制后,prepare阶段不变,只针对commit阶段,将commit阶段拆分为三个过程:

  • flush阶段:多个事务按进入的顺序将binlog从cache写入文件(不刷盘)。
  • sync阶段:对binlog文件做fsync操作(多个事务的binlog合并一次刷盘)。
  • commit阶段:各个事务按顺序做InnoDB commit操作。

上面的每个阶段都有一个队列,每个阶段有锁进行保护,因此保证了事务写入的顺序,第一个进入队列的事务会成为leader,领导所在队列的所有事务,全权负责整队的操作,完成后通知队内其他事务操作结束。