Mysql中隔离级别可重复读解决不可重复读的底层原理是什么?
结论
解决不可重复读的原理是:可重复读隔离机制在同一个事务中,第一次读取数据时会创建一个ReadView,该事务后续的所有普通SELECT查询(非锁定读)都会基于这个视图来获取数据,从而保证每次读到的都是同一个快照版本的数据,不受其他事务提交的影响。
不可重复读的意思是一个事务内,多次读取同一个数据,结果不一致。因为在两次读取之间,有其他事务提交了修改(UPDATE)。
Mysql默认隔离级别可重复读解决了脏读,不可重复读。意思就是只能读取到事务已经提交的数据。 在同一个事务里,无论读多少次,同一条数据的结果都是一样的。
MVCC的组成部分
1.隐藏字段
每条记录会有两个隐藏字段
DB_TRX_ID(6字节):记录创建或最后一次修改该行数据的事务ID。
DB_ROLL_PTR(7字节):回滚指针,指向该行数据的上一个历史版本(在Undo Log中)。
DB_ROW_ID(6字节):隐含的自增ID(如果没有主键)。
2.Undo Log(回滚日志)
当数据被修改时,InnoDB不仅会将新值写入,还会将修改前的旧版本数据(前置镜像)存入Undo Log。
通过DB_ROLL_PTR,一行数据的所有旧版本可以被串联成一个链表。
示例:
假设我们有一张表 users,其中有一行数据:
id name age
1 张三 20
事务A (trx_id = 100) 开始,并将 age 从 20 更新为 25。
1.InnoDB不会直接覆盖原数据。
2.它会将当前行数据(id=1, name=‘张三’, age=20)的拷贝存入Undo Log。
3.在数据库的当前页中,将这行数据的 age 更新为 25。
4.InnoDB会在这行数据的表头信息中,记录下修改它的事务ID trx_id=100,以及一个指向Undo Log中旧版本数据的回滚指针
roll_pointer。
trx_id: 100, roll_pointer -> 【Undo Log记录:事务100修改前】(id=1, name='张三', age=20, trx_id=?)roll_pointer -> NULL
3.Read View(读视图)
当一个事务执行第一次普通SELECT查询时,InnoDB会为它生成一个快照Read View。
Read View 主要包含以下信息:
m_ids:生成Read View时,系统中活跃(未提交)的事务ID集合。
min_trx_id:m_ids中的最小值。
max_trx_id:生成Read View时,系统应该分配给下一个事务的ID。
creator_trx_id:创建该Read View的事务自己的ID。
可见的条件是:
1.该版本的 DB_TRX_ID < min_trx_id,说明这个版本在当前Read View创建之前就已经提交了。
2.该版本的 DB_TRX_ID 在 m_ids 列表中不存在,说明这个版本在创建Read View时已经被提交了。
示例
假设有两个事务:
事务A(ID=100):更新了一行数据 name=‘Alice’-> name=‘Bob’,但尚未提交。
事务B(ID=200):发起一个 SELECT查询,要读取这行数据。
过程如下:
事务B开始,执行 SELECT,生成一个 Read View。
m_ids= [100] (因为事务A(100)是活跃的)
min_trx_id= 100
max_trx_id= 201
creator_trx_id= 200
事务B去读取数据行。当前最新版本是由事务A(ID=100)修改的 name=‘Bob’。
事务B使用 Read View 进行可见性判断:
当前版本的 DB_TRX_ID是 100。
100 在 m_ids列表中吗?在。
根据规则4,这个版本不可见。
事务B顺着回滚指针找到上一个版本 name=‘Alice’,它的 DB_TRX_ID是 50(假设是更早的一个已提交事务)。
再次判断:
DB_TRX_ID(50) < min_trx_id(100) 吗?是。
根据规则2,这个版本可见。
所以,事务B读取到的是 name=‘Alice’,而不会读到事务A未提交的修改 name=‘Bob’。
