MVCC
- 多版本数据并发控制 Mul-Version Concurrency Control),对数据的每一次修改都会生成一个新的版本(即便事务没有commit),注意,很容易理解成同时在多个事物中修改同一行数据,实际上如果当前已有事物对某一行数据进行修改且没有commit,那么其他事物是无法对该数据进行修改的,这也意味着不可能产生undo log
- mvcc解决的是事务原子性和事务隔离性问题
- mvcc解决的是读写不互斥的问题(而不是写与写),避免并发阻塞
- 删除数据的undo log会将一个delete_flag设置成true,如果匹配到这个删除的undo log就直接不返回数据了,这就是为什么读到删除的数据和修改的数据被称之为不可重复读,而新增数据被称之为幻读,因为修改和删除都存在原始数据,意味着有原始的undo log版本,而新增数据没有
- 隐藏字段事务id和数据上一个版本的地址

如何保证原子性的
事务回滚,回退到上一个版本的undo log数据
一致性视图(Consistent View)
- 一致性视图是 MVCC 中的一个重要概念,用于实现快照读的可重复读(Repeatable Read)隔离级别。它记录了事务开始时系统中所有活跃事务的状态。并非是读取数据时的事务真实状态,而是事务开启时的系统事务状态快照,每次读会使用同一个CV,而读提交会每次生成一个CV
- 通过一致性视图,InnoDB 实现了在快照读中的可重复读隔离级别,确保事务在读取过程中看到的总是相同的数据视图。
一致性视图的结构:
- 低水位线(Low Water Mark): 当前系统中最早开始但尚未提交的事务 ID。
- 高水位线(High Water Mark): 当前系统中最大已分配的事务 ID,可以是未提交状态也可以是提交状态。
- 活跃事务列表: 记录在一致性视图生成时,系统中所有活跃事务的 ID。
一致性视图的使用规则:
- 当前版本的事务 ID 小于低水位线: 该版本在一致性视图生成前已提交,可以被读取。
- 当前版本的事务 ID 大于高水位线: 该版本在一致性视图生成后才开始,不能被读取。
- 当前版本的事务 ID 在活跃事务列表中: 该版本在一致性视图生成时正活跃,不能被读取。
- 当前版本的事务 ID 不在活跃事务列表中,且介于低水位线和高水位线之间: 该版本已提交,可以被读取。
读提交为什么也需要生成一致性视图
undo log没有数据提交标记:数据是没有提交状态的,只有事务才有,所以undo log中是没有记录数据是否提交的标记,也不会有事务提交的标记,要判断当前数据是否提交,需要判断当前事务是否提交,数据库系统是记录了事务状态的
避免读到中间数据:假设场景修改id=1的数据的x字段,A1:修改数据x=1 --> A2:修改数据x=2 --> B1:读取该数据的多版本,读到x=2版本,判断事务状态未提交,不读取 --> A3:提交事务 --> B1:读取到x=1的版本,判断此时事务状态已提交,读取x=1,结果没有保证数据一致性,所以读提交也需要每次都生成一次性视图,就是为了保证同一个事务的数据必须要处于同一个状态,不能在读取过程中事务状态发生了改变
为什么当前读没有生成一致性视图
有锁:当前读没有生成一致性视图为什么就能够避免读到中间数据呢,这是因为当前读是带有锁的,如果其他事务对数据有修改,当前读是会被lock的
快照读
使用的一致性视图:快照读就是通过一致性视图来实现的,目的是为了实现事务的RR隔离级别
快照读是大部分场景下能够解决幻读:快照读生成了一致性视图,那么即便后面有事务新增了数据并提交了数据,由于使用的是开启事务时的事务状态,所以根本不会读到新增的数据,从这点上看是不会产生幻读问题,大部分场景也是如此
快照读不能解决幻读的场景:事务A、B并发,A1:读id >= 10的数据,假设目前只有一条id=10 --> B1:新增一条id=11的数据并提交-->A2:修改id=10的数据 --> A2:读取id >= 10的数据,此时就会将B新增的数据读到,这是以为update操作没有快照概念,update是能够修改到id=11的数据的,那么此时由于最新版本的数据是当前事务修改的,所以基于一致性视图也能够读到新增的这条数据。
如何保证不会读到删除的数据:在事务隔离级别的概念中,读到新增的数据叫做幻读,读到删除的数据叫做不可重复读,将上面case中的新增改为删除,事务A还能否读取到事务B删除的数据呢?由于事务B删除了数据,对于事务A的update就是无效的,所以不会产生一条基于事物A的id=31的数据,所以是读不到删除的数据的
如何保证不会读到修改的数据:还是上面的case,如果B是对id=31的数据进行修改操作,由于A也对数据进行了修改操作,所以肯定能够读到A对数据的修改,但这是符合预期的,并没有读到B对id=31的修改
当前读
为什么RR不能解决幻读问题呢:因为在RR中可能会使用当前读,那就就能够读到提交的数据
什么时候用快照读,什么时候用当前读:普通的读都是快照读,只有使用的时候才会是当前读select ... for update`或者`select ... in share mode
当前读能解决不可重复读的问题吗:当前读也是RR隔离级别下的一种读模式,那么必然是能够解决不可重复读问题的,但从其不读快照的规则来看,应该是可以读到事务过程中提交的数据,那么它是怎么解决不可重复读问题的呢?是因为锁,当前读会对数据加排它锁,所以其他事务是无法对该数据进行修改的(删除也是一种修改),从而保证了可重复读
当前读与快照读混合:RR隔离级别下,如果在事务A中,A1:快照读 --> B1:修改数据并提交 --> A2:当前读,那么A2是会读到事务B修改的数据,所以严格
RR为何不能解决幻读问题
- 快照读中,如果事务中存在对新增数据的修改那么就会读到新增的数据
- 当前读不使用一致性视图,会读到最新提交的数据