探究Mysql可重复读隔离级别的实现以及该级别下如何避免幻读

it2025-12-31  1

文章目录

前言一、基本概念1.mvcc2.当前读和快照读3.undo log4.RR和RC 二、RR级别下innodb避免幻读的方法。1.探究RR级别下和RC级别下读取数据的区别2.RR级别下避免幻读的实质,行锁+gap锁

前言

在学完数据库的索引和锁的一些知识后,今天我们来探讨下Mysql的innodb存储引擎在可重复读隔离级别下是如何避免幻读的。

一、基本概念

先来看几个基本概念。

1.mvcc

MVCC(Mutil-Version Concurrency Control),就是多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。

2.当前读和快照读

当前读就是加了锁的增删改查语句,它读取的是记录的最新版本。例如你执行update语句的时候,就会先执行当前读。 快照读就是不加锁的select操作,它可能读到的不是最新数据,而是之前版本的数据。一般你开启一个事务后的select语句,就是快照读。

3.undo log

undo日志用于存放数据修改被修改前的值。

4.RR和RC

可重复读和已提交读的英文简称。

二、RR级别下innodb避免幻读的方法。

1.探究RR级别下和RC级别下读取数据的区别

我们来探究下RR隔离级别和RC隔离级别下的快照读

在InnoDB引擎表中,它的聚簇索引记录中有两个必要的隐藏列: 一个是TRX_ID,即每次操作修改该行记录的事务id。 一个是ROLL_PTR,它指向被修改行数据上一个版本在undo log中的地址。

再来介绍下ReadView,ReadView就是事务进行快照读操作的时候生产的读视图,在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,ReadView中会有个列表来存储我们系统中当前活跃着的读写事务ID,也就是开始了但还未提交的事务,每个事务开启时,都会被分配个ID,新事务的ID大于旧事务。 通过这个列表我们可以判断当前事务能看到的是哪个事务操作前的数据。我们读取某数据时,会将数据的事务id取出,与列表中的其他id进行对比。如果该数据的事务id比列表中的id都小,则表示是稳定版本,可以读取。如果该数据的事务id比列表中的id都大,则去undo log中去它的之前版本出来比对,直到取到稳定版本为止。如果该数据事务id在列表范围内,则判断该事务id是否在列表内,如果在则说明该事务还未被提交,不能访问,反之则可以访问。

我们来举个例子,假设一条数据如下图: 这是一条事务id为50的数据。这时候一个事务id为60的事务修改了它,于是他变成了 这时我们再来修改这条记录,我们开启事务id为100的事务来修改这条记录但是先不提交,这时候数据变成了 此时我们来对这条数据进行快照读,因为100还没提交,在ReadView列表内,所以是不稳定版本,读取到的数据是事务id为60的小明1。 这时我们把事务id为100的数据提交,再开启一个新的事务id,假设为110,去再次修改数据,则修改后数据如图: 这时,我们再次去对该数据进行快照读。 注意,此时RC和RR隔离条件下的区别就显示出来了: 如果是在RC下,我再次快照读,由于100的事务已经提交,所以我读到的肯定是事务id为100的记录。 而在RR级别下,我们第一次调用快照读,创建了ReadView,后边再次进行快照读时使用的仍是第一次读取时创建的快照,所以我读到到的数据和第一次一样,还是事务id为60的小明1。之后无论修改多少次,我每次调用快照读读出来的数据都是第一次的数据。

不过有一点要注意,如果我再自己本身的事务中修改了数据,快照读读出来的会是修改了的数据。

上文中我们探究了RR和RC隔离级别下读取数据的区别,下面我们再来看看RR级别下避免幻读的实质。

2.RR级别下避免幻读的实质,行锁+gap锁

行锁就是只对数据库的某一行上锁,那什么是gap锁呢? gap锁,即间隙锁,是InnoDB在可重复读的隔离级别下为了解决幻读问题引入的锁机制,它可以实现在对行和行之间的间隙进行加锁。幻读问题的存在是因为在新增或者更新时如果进行查询,会出现不一致的现象,这时单纯的使用行锁无法满足我们的需求,我们需要对一定范围的数据加锁,即加gap锁,理论上是可以防止幻读发生。

下面来分析下gap锁的上锁情况:

当你在rr模式下开启一个事务,然后在第一次进行快照读时; 如果where条件全部命中,则不会用Gap锁,只会加记录锁,原因是因为查询条件是不重复的,所以对数据的增删不会影响到查询条件内的数据,行锁已经足以保证幻读现象的发生。 如果where条件部分命中或者全不命中,则会加Gap锁,因为可能会产生幻读现象。 如果sql走的是非唯一索引或者不走索引,则会加Gap锁。

Mysqlgap锁+行锁的方式,避免了rr情况下幻读的发生。

最新回复(0)