叶一舟 发表于 2023-6-13 22:44:07

MySql的MVCC机制

事务隔离级别遗留问题:


[*]在读已提交的级别下,事务B可以读到事务A持有写锁的的记录,且读到的是未更新前的,为何写读没有冲突?
[*]可重复读级别,事务B可以更新事务A理论上应该已经获取读锁的记录,且更新后,事务A依然可以读到数据,为何读-写-读没有冲突?
[*] 在可重复读级别,幻读没有产生
  其中,前两个问题就是因为mvcc机制(读锁的一种优化机制),通过不加读锁,避免读写冲突,进而提高了性能。
为什么要有MVCC机制?


[*]在读已提交的级别下,由于是给读加锁来保证读已提交, 如果事务A持有写锁,为了保证读已提交,事务B必须等待事务A提交之后才可以读;其他的读事务也是这样的情况,效率太低
[*]在可重复读级别,为了保证可重复读,如果事务A持有读锁,为了第二次读到的一样,其他所有写事务必须等待读完才可以,同样效率低
那么很自然的想到,无论读事务是先产生还是后产生,如果这个时候还存在写事务没有执行,或者需要执行;那么就应该让读事务读到目前最新的值,且写事务可以更新;只不过读事务在写事务提交更新后,依据隔离级别是否可见最新更新即可。这就是MVCC机制的核心能力,将读锁干掉。
MVCC机制核心组件

MVCC机制由版本链、undolog、readview三大核心构成版本链

猜测很多人第一次看到MVCC的版本都是和我一样在各种各样的博客文章上,或者可能是在一些课程专栏或者《高性能mysql》这本书的mvcc部分看到的,那么在你的理解中,版本的底层是什么样子呢?innodb引擎数据库中的每一条记录上,我们都可以认为上面有3个隐藏字段,分别是DB_ROW_ID(不在此次讨论范围),DB_TRX_ID和DB_ROLL_PTR,如下图一样
 
在我的理解中,DB_TRX_ID就是插入或者更新时,当前事务的trx_id,由全局事务管理器分配的递增的一个id;DB_ROLL_PTR存储的undolog中当前记录上一个版本的指针,先姑且记住这是一个指针。当插入一条记录时在这条记录的DB_TRX_ID填入当前事务的id,由于没有历史版本,所以DB_ROLL_PTR为空当更新一条记录时由于这个时候存在历史版本,所以需要将老版本的数据写到undolog里,然后构建指针,将DB_TRX_ID更新为当前事务的id,将DB_ROLL_PTR更新为刚才构建的指针,以及更新需要更新的字段。当删除一条记录时(这个不太确定,主观猜测)猜测是将老记录写到undolog,然后构建指针,新记录DB_TRX_ID更新为当前事务的id,将DB_ROLL_PTR更新为刚才构建的指针,但是没有需要更新的字段。而且mysql不会立即删除,记录上有一个info_bits字段,会标记上删除标识(REC_INFO_DELETED_FLAG),后续由purge线程(不了解,姑且认为是个scheduleTask吧)删除这样,当多次更新之后,新记录存储的永远都是最新操作的事务id,并通过指针指向了老版本,老版本还指向了更老的版本...等等,最终构成了一个版本链Readview

理论:

在周志明老师的凤凰架构(或者极客时间的‘周志明的软件架构课’)中对mvcc简单介绍到隔离级别是可重复读:总是读取 CREATE_VERSION 小于或等于当前事务 ID 的记录,在这个前提下,如果数据仍有多个版本,则取最新(事务 ID 最大)的。隔离级别是读已提交:总是取最新的版本即可,即最近被 Commit 的那个版本的数据记录。在mysql官网中是这么描述的If the transaction isolation level is REPEATABLE READ (the default level), all consistent reads within the same transaction read the snapshot established by the first such read in that transaction. You can get a fresher snapshot for your queries by committing the current transaction and after that issuing new queries.With READ COMMITTED isolation level, each consistent read within a transaction sets and reads its own fresh snapshot.翻译:隔离级别是可重复读:在同一个事务中,一致性读总是去读在该事务第一次读取时生成的快照。隔离级别是读已提交:事务中的每次读取都取自己新生成的快照。相比之下,周老师形容的更贴近隔离级别的概念上,官方的描述则是底层的具体实现逻辑。两者结合一下就是可重复读:通过在每个事物只读取第一次select时生成的快照和undolog比较,根据一个可见性规则判断,是否可以读当前版本的记录,可以就返回,不行就继续比较再上一个版本,直到最老的版本;读已提交:除了每次读取都会使用最新的快照,后面的都和可重复读的逻辑一样。为什么我这里说的是可见性规则呢?是因为周老师描述里“总是读取 CREATE_VERSION 小于或等于当前事务 ID 的记录”很容易错误的理解为当前版本记录里的trx_idrw_trx_ids抄过来的ids_t m_ids;/** The read should not see any transaction with trx id >= thisvalue. In other words, this is the "high water mark". *///赋值是即将分配的下一个事务id,所以大于等于这个id的记录对当前事务来说都是不可见的trx_id_t m_low_limit_id;/** The read should see all trx ids which are strictlysmaller (
页: [1]
查看完整版本: MySql的MVCC机制