Mysql MVCC实现

背景

并发事务可能产生的问题:

  • 读+读,并发读不会有问题
  • 读+写,并发读写可能会发生脏读、不可重复读、幻读
  • 写+写,并发修改同一行数据,可能产生数据丢失(会滚丢失、覆盖丢失)等问题

MVCC定义

MVCC(Mutil Version Concurrency Control)多版本并发控制,是一种并发访问的机制(非具体实现),广泛应用于数据库管理系统,比如Mysql、Oracle、Postgresql等,实现对数据库的并发访问。本质就是一行数据具有多个不同版本的记录。

Mysql的InnoDB引擎实现了MVCC机制,用来处理读写冲突,做到非阻塞并发读,提升并发效率。

快照读和当前读

当前读

读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

  • select lock in share mode(共享锁) 读读不冲突、读写、写写冲突
  • select for update ; update, insert ,delete(排他锁) 读读、读写、写写冲突

快照读

顾名思义,就是读取undo log中的某一版本的快照,读到的数据可能不是最新的,但是可以不加锁就可以读到数据

  • 读读不冲突、读写不冲突
  • 写写冲突

MVCC实现原理

MVCC的目的就是多版本并发控制,在数据库的实现,就是为了解决读写冲突,它的实现原理主要依赖记录中的3个隐式字段,undolog,Read View来实现的。

隐式字段

每行记录除了我们自定义的字段之外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

  • BD_TRX_ID: 6byte,最近修改(修改/插入)的事务ID:记录创建该记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR: 7byte,回滚指针,指向这条记录的上一个版本(存储在rollback segment里)
  • DB_ROW_ID: 6byte,隐含自增ID,如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引

在这里插入图片描述

undo log

在修改数据的时候,会向 redo log 中记录修改的页内容(为了在数据库宕机重启后恢复对数据库的操作),也会向 undo log记录数据原来的快照(用于回滚事务)。undo log有两个作用,除了用于回滚事务,还用于实现MVCC

  • insert log:代表事务在 insert 新记录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
  • update undo log:事务在进行 update 或 delete 时产生的 undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除

版本链

1.插入一条记录

在这里插入图片描述

2.修改记录

在这里插入图片描述

  1. 加锁
  2. copy到undo log,作为旧记录(当前行的copy副本)
  3. 修改age,trx_id(从1开始递增);回滚指针指向副本
  4. 提交事务,释放锁
3.修改记录

在这里插入图片描述

  1. 加锁
  2. copy到undo log,作为旧记录(当前行的copy副本),已经有undo log,此副本作为链表表头插入的undo log的头节点
  3. 修改age,trx_id(从1开始递增);回滚指针指向副本
  4. 提交事务,释放锁

Read View读视图

Read View是在对数据进行快照读时,会产生的一个”一致性读视图“。

属性:

  • m_ids:活跃事务id列表,当前系统中所有活跃的(也就是没提交的)事务的事务id列表。
  • min_trx_id:m_ids 中最小的事务id。
  • max_trx_id:生成 ReadView 时,系统应该分配给下一个事务的id(注意不是 m_ids 中最大的事务id),也就是m_ids 中的最大事务id + 1 。
  • creator_trx_id:生成该 ReadView 的事务的事务id。

这些属性组成了当前事务的一致性视图(Read View),而数据版本的可见性规则,就是基于数据的 row trx_id 和这个一致性视图的对比结果得到的。

Read View可见性算法

把数据的最新记录中的 DB_TRX_ID取出来,与Read View对比,如果不符合可见性,那就通过undo log 取下一个版本对比,直到找到满足可见性的版本数据。
在这里插入图片描述

  • 当【版本链中记录的 trx_id 等于当前事务id(trx_id = creator_trx_id)】时,说明版本链中的这个版本是当前事务修改的,所以该快照记录对当前事务可见。
  • 当【版本链中记录的 trx_id 小于活跃事务的最小id(trx_id < min_trx_id)】时,说明版本链中的这条记录已经提交了,所以该快照记录对当前事务可见。
  • 当【版本链中记录的 trx_id 大于下一个要分配的事务id(trx_id > max_trx_id)】时,该快照记录对当前事务不可见。
  • 当【版本链中记录的 trx_id 大于等于最小活跃事务id】且【版本链中记录的trx_id小于下一个要分配的事务id】(min_trx_id<= trx_id < max_trx_id)时,
    • 如果trx_id m_ids中,说明生成 ReadView 时,修改记录的事务还没提交,所以该快照记录对当前事务不可见;
    • 如果trx_id不在m_ids中,说明生成该版本的事务已经提交,对当前事务可见

隔离级别

  • 读未提交(READ UNCOMMITTED)
  • 读已提交(READ COMMITTED)
  • 可重复读(REPEATABLE READ)
  • 串行化(SERIALIZABLE)

mvcc只在读已提交和可重复读两种隔离级别生效

  • 读已提交
    • 事务开启后,每次select都会生成一个Read View,可以读到别的事务已经提交的数据
  • 可重复读
    • 事务开启后,只在第一次select时生成Read View,之后的select都基于此视图做可见性判断。

长事务

为什么要避免长事务

  • 长事务可能存在很老的Read View,如下图的事务1和2,这些视图很可能访问任何数据,在这个事务提交前,它可能用到的回滚记录都不能清理,需要保留,占用大量存在空间
  • 长事务占用锁资源,长时间不释放锁,可能拖垮整个库
    在这里插入图片描述
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值