Undo log与 MVCC机制

Undo日志

INSERT语句的undo log日志

对应的类型是TRX_UNDO_INSERT_REC

  • 这条日志的开始位置
  • 主键的各列长度和值
  • 表id
  • undo log日志编号
  • undo log日志类型
  • 这条日志的结束位置

在这里插入图片描述

假设现在需要回滚一条insert语句,只需要把undo log拿出来,就知道在哪个表里插入的数据,主键是什么,直接定位到哪个表和主键对应的缓存页,从里面删除掉之前insert语句插入进去的数据就可以了。

MySQL运行时多个事务同时执行

read uncommitted,读未提交,不允许脏写(第一类丢失更新)发生,因为它不允许两个事务在没提交的情况下去更新同一行的数据的值。

RC,RR,serialize

MySQL是如何支持四种隔离级别

MySQL依托于MVCC机制,就能让RR级别避免不可重复读和幻读的问题。

MySQL的MVCC机制,多版本并发控制机制

每条数据都有两个隐藏的字段,一个是trx_id,一个是roll_pointer,trx_id就是最近一次更新这条数据的事务id,roll_pointer就是指向更新了这个事务之前生成的undo log。

Undo log与 MVCC机制_第1张图片

上图是多事务并发时候数据行以及undolog的内容。

基于undo log多版链条实现的ReadView机制

执行一个事务的时候,生成一个ReadView,里面比较关键的东西有4个:

  • m_ids,说明此时有哪些事务在MySQL里面执行还没提交‘
  • min_trx_id,就是m_ids里最小的值
  • max_trx_id,就是mysql下一个要生成的事务id
  • creator_trx_id,就是这个事务的id

一开始的时候,数据库如下:

Undo log与 MVCC机制_第2张图片

这时候,两个事务并发执行过来了,事务A(id=45)和事务B(id=59),事务A直接开启一个ReadView,ReadView里的m_ids就包含了事务A和事务B的两个id,45和59。然后min_trx_id就是45,max_trx_id就是60,creator_trx_id就是45,是事务A自己。事务A第一次查询这行数据时,判断当前这行数据trx_id是否小于ReadView中的min_trx_id,发现trx+id=32,是小于ReadView里的min_trx_id就是45的,说明事务A之前,这个事务早已经提交了,所以可以查到这行数据。事务B开始执行,将数据修改为值B,trx_id设置为自己的id,59。同时roll_pointer指向了修改之前生成的一个undo log,接着这个事务B就提交了。如图所示:

Undo log与 MVCC机制_第3张图片

事务A再次查询,发现trx_id为59,那么这个trx_id是大于ReadView的min_trx_id(45),和小于ReadView里的max_trx_id(60)的,说明更新这条数据的事务,很可能就是跟自己差不多同时开启的,于是会看一下trx_id=59,是否在ReadView的m_ids列表里。查找,发现果然是。于是顺着undo log日志链条往下找,找到最近一条undo log,trx_id是32,发现trx_id=32,是小于ReadView的min_trx_id(45)的,说明undo log版本必然是在事务A开启之前就执行提交的,于是就查询此undo log里的值。这就是undo log多版本链的作用,它可以保存一个快照链条,让事务可以读到之前的值。

读已提交如何基于ReadView机制实现的

RC级别的核心是每次发起查询都重新生成一个ReadView。第一次查询的时候,发现数据的trx_id在自己的min和max之间和m_ids之中,所以它是一个活跃的事务修改的,并且此时尚未提交,所以它会顺着undo log版本链查找以及提交的事务。第二次查询的时候,重新开启一个新的READ VIEW,发现数据的trx_id不在m_ids中,说明该事务已经提交,所以直接读取数据行的数据。这就是RC级别的实现原理。

可重复读如何基于ReadView实现的

可重复读的核心是只在事务开始时生成一个ReadView,后续再次读的时候,即使事务B提交了,但是事务A它的ReadView还是刚开始事务时的,所以认为B还是活跃的,所以继续沿着undo log版本链查询,读取到始终一样。MySQL的可重复读还解决了幻读,因为新插入的数据记录的trx_id要么大于事务A的max_trx_id,要么在m_ids之中 (而且非本身)所以认为插入的数据记录时不能被读取的。

事务并发写

之前我们讨论的其实都是别人在更新数据的时候,你怎么读的问题,脏读,不可重复,幻读。接下来看看写的问题。

Undo log与 MVCC机制_第4张图片

事务A先加锁了,等待锁状态为false,事务B想要修改同一行数据,也尝试获取锁,将自己的等待状态设置为true,事务A修改完之后,将事务B的等待状态改为false,事务B就获取到锁了。

MySQL的独占锁和共享锁

当有人在更新数据的时候,其他事务读取这行数据,默认情况下需要枷锁吗?不用,因为基于mvvc机制,不会去读最新的值,这样子也避免了频繁加锁。如果要自己加锁也可以:

 -- 加共享锁
 select * from table lock in share mode
 -- 加排他锁
 select * from table for update

在数据库里,哪些操作会导致表级别加锁

DDL语句会阻塞所有增删改操作,但这是通过MySQL通用的元数据锁实现的,也就是Metadata Locks,这不是表锁的概念。表锁其实InnoDB存储引擎的概念,InnoDB存储引擎提供了自己的表级锁,跟DDL的元数据锁不一样。

表锁和行锁之间的关系以及互斥规则

如果事务在表里执行增删改操作,那在行级会加独占锁,此时同时会在表级加一个意向独占锁,如果有事务在表里执行查询操作,那么会在表级别加一个意向共享锁,而不会加行级共享锁。意向独占锁和意向共享锁他们时不会互斥的。

Undo log与 MVCC机制_第5张图片

上面描述的是表级独占锁和表级共享锁,以及更新数据和查询数据库默认自动加的意向独占锁和意向共享锁,他们互相之间的互斥关系。实际上查询一行数据时,MySQL是不会帮我们加表级或行级锁的。

你可能感兴趣的:(MySQL,数据库,mysql)