Mysql之MVCC

什么是 MVCC

MVCC, 即多版本并发控制。MVCC 的实现,是通过**保存数据在某个时间点的快照来实现的**,提高了数据库的读写性能,以下文章都是围绕 InnoDB来说的,因为mylsam不支持事务
根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。
同一行数据平时发生读写请求时,会上锁阻塞,但mvcc用更好的方式去处理读写请求,做到在发生读写请求冲突时不用加锁。
读指的是快照读,而不是当前读,当前读是一种加锁的操作,是悲观锁

Mysql InnoDB下的当前读和快照读

当前读

读取数据库记录,读取当前最新版本,会对当前读取数据进行加锁,防止其他事务修改数据,是悲观锁的一种操作

如下操作都是当前读:

  • select lock in share mode(共享锁)
  • select for update (排他锁)
  • update(排他锁)
  • insert(排他锁)
  • delete(排他锁)
  • 串行化事务隔离级别

快照读

快照读是基于多版本并发控制,即mvcc,既然是多版本,那么快照读到的数据不一定是当前的最新数据,可能是之前历史版本的数据

MVCC 的实现原理

对于 InnoDB ,聚簇索引记录中包含 3 个隐藏的列:

  • ROW ID:隐藏的自增 ID,如果表没有主键,InnoDB 会自动按 ROW ID 产生一个聚集索引树。
  • 事务 ID:记录最后一次修改该记录的事务 ID。
  • 回滚指针:指向这条记录的上一个版本。

我们拿上面的例子,对应解释下 MVCC 的实现原理,如下图:

在这里插入图片描述

版本链

每次更新记录,旧值放到undo日志,根据roll_pointer连成一条版本链,头节点是当前记录的最新值。另外还包含每个版本对应的事务id。

InnoDB聚簇索引的两个必要隐藏列:trx_id和roll_pointer,解释如下

trx_id

一个事务每次对某条聚簇索引改动时,会把改事务的id赋值给trx_id。

roll_pointer

每次对某条聚簇索引改动时,会将旧版本写到undo日志中。这个隐藏列相当于一个指针,可以通过它找到该记录修改前的信息。(Insert操作的undo日志没有该属性,insert undo只在事务回滚时发挥作用,事务提交后就没用了。

通过版本链来控制并发事务访问相同记录时的行为,称这种机制为多版本并发控制。

总结

MVCC 实现的原理大致是,InnoDB 每一行数据都有一个隐藏的回滚指针,用于指向该行修改前的最后一个历史版本,这个历史版本存放在 undo log 中。如果要执行更新操作,会将原记录放入 undo log 中,并通过隐藏的回滚指针指向 undo log 中的原记录。其它事务此时需要查询时,就是查询 undo log 中这行数据的最后一个历史版本。

MVCC 最大的好处是读不加锁,读写不冲突,极大地增加了 MySQL 的并发性。通过 MVCC,保证了事务 ACID 中的 I(隔离性)特性。

ReadView

读未提交 直接读最新的版本就好了,但是 读已提交可重复读 都必须保证读到已提交的事务修改过的记录,即另一个事务已经修改了记录但是未提交,则不能读取最新版的记录。

核心问题:需要判断版本链中的哪个版本是当前事务可见的,通俗来说,就是判断版本链中要去选择哪个版本

ReadView的四个重要内容

当执行查询sql时会生成一致性视图read-view,查询的数据结果需要跟read-view做对比从而得到快照结果。

m_ids:在生成ReadView时,当前系统活跃读写事务的事务id列表(没有commit)。
min_trx_id:在生成ReadView时,当前系统活跃读写事务的最小事务id;也就是m_ids中的最小值。
max_trx_id:在生成ReadView时,系统应该分配给下一个事务的id值。
creator_trx_id:生成该ReadView的事务的事务id。只有执行insert、update、delete操作时才会分配事务id,否则该值默认为0。

举个栗子: 如果 m_ids中包含1、2、3,那么min_trx_id为1,creator_trx_id为4

如何判断版本可见

在这里插入图片描述
在这里插入图片描述

访问版本的 trx_id 与creator_trx_id相同(事务访问自己的版本),可以访问该版本。
访问版本的 trx_id 小于min_trx_id,表明访问版本的事务已经commit了,在当前事务生成ReadView之前,是可以访问的
访问版本的trx_id大于max_trx_id,读取事务的时候,我们只能读取版本链中的数据,访问版本的事务id已经超出了版本链,所以不可访问
访问版本trx_id在min_trx_id和max_trx_id之间,如果等于m_ids中的一个值,是不可以访问的,反之可以(举个栗子:m_ids是1 3 4 5,如果trx_id为2,可以访问;为3,不能访问)

读已提交和可重复读生成ReadView的时机

读已提交(解决脏读):每次读取数据(select)前都生成一个ReadView。
可重复读(解决脏读和不可重复读,幻读能否解决是不确定的):是以一个事务为单位,只在第一次读取(select)数据时生成ReadView。

不可重复读:事务A第一次查询id=1的name=’ 张三 ‘,过了一段时间后,事务A第二次查询id=1的name=’ 张大三 ‘,那是因为在这段时间内,事务B更新(update)了id=1的name=’ 张大三 ,事务B提交,由此事务A插查询到前后两次的数据不一致

视频:MySQL脏读、幻读、不可重复读你能分清吗?

二次索引与MVCC

只有在聚簇索引中才有 trx_id 和 roll_pointer 隐藏列,那么二级索引(非聚簇索引)如何判断可见性呢?

二级索引页面的Page Header部分,有一个属性 PAGE_MAX_TRX_ID,执行增删改查时,如果执行操作的事务id大于PAGE_MAX_TRX_ID的值,则将其值设置为执行操作的事务id。即 PAGE_MAX_TRX_ID 代表修改该二级索引页面最大的事务id。
执行select时,如果ReadView的 min_trx_id 大于 PAGE_MAX_TRX_ID,则说明该页面的值对ReadView可见;如果小于,则需要回表进行判断。
利用二级索引中的主键进行回表操作,得到聚簇索引的记录后,再按照聚簇索引的方式从第一个版本开始,依次判断可见性。

可重复读是否能解决幻读问题?

mysql的可重复读在MVCC和锁机制下尽可能保证幻读问题,但是并不能完全禁止幻读。

特殊情况下,仍然可能出现幻读问题:

T1执行select生成ReadView
T2新插入一条记录,并且提交
ReadView不能阻止T1执行UPDATE或者DELETE语句来改动这条新插入的记录。(由于T2提交,改动这条记录并不能造成阻塞)
T1修改这条记录,这条记录的trx_id变成了T1的id。
之后T1再次查询时,在查询结果中就可以发现这条记录了

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值