mysql-MVCC
MVCC
多版本并发控制,通过数据行的多个版本管理实现数据库的并发控制,使得在事务隔离级别下执行一致性读有了保证,换言之,为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。
快照读 当前读
主要是为了提高数据库的并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突,也能做到不加锁非阻塞并发读,读是指快照读,而非当前读。当前读是悲观锁的实现。
- 快照读:又叫一致性读,读取的是快照数据,不加锁的简单的select都属于快照读,是基于提高并发性能的考虑,快照读的实现是基于MVCC,既然基于多版本的,快照读可能读取到的不一定是数据的最新版本,而有可能是历史版本,前提隔离级别不是串行级别(退化成当前读)
- 当前读: 读取的是记录的最新版本,读取时还要保证其它并发事务不能修改当前记录,会对读取的记录进行加锁,加锁的增删盖查都会进行当前读。
再谈隔离级别
在mysql中,默认的隔离级别是可重复读,可以解决脏读不可重复读的问题,MVCC不采用锁机制,通过乐观锁的方式解决不可重复读和幻读问题们可以在大多数情况下替代行级锁,降低系统开销。
undo log 的版本链:对于InnoDB存储引擎的表,聚簇索引记录中包含两个必要的隐藏列
trx_id | roll-pointer | 1 |
---|---|---|
每次事务对聚簇索引改动时就把该事务的事务ID赋值给它 | 每次对记录进行改动时候,都会把旧的版本写入undo log | 2 |
注意不会出现在两个事务中交叉更新同一条记录(脏写),使用锁机制保证不会发生。每次对记录进行改动,都会记录一条undo日志,每条undo日志都有roll-pointer ,将undo日志连起来形成一个链表。对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本会被roll-pointer链接形成一个链表,称之为版本链,版本链的头结点就是当前记录的最新的值。每个版本还包含生成该版本时对应的事务id。
MVCC实现原理之readview:隐藏字段、undo log、readview
什么是readview
在mvcc机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在undo log中,如果一个事务想要查询这个行记录,需要读取那个版本的记录? readview就是事务在使用MVCC机制进行快照读操作时产生的读视,当事务启动时,会生成数据系统当前的一个快照,innoDB为每个事务构造了一个数组,用来记录并维护系统当前活跃(启动但没有提交)事务的id。
设计思路
使用RC RR隔离级别的事务,都必须保证读到已经提交了的事务修改过的记录,假如另一个事务已经修改了记录但是尚未提交,是不能读取最新版本的记录的,因此需要判断一下版本链中哪个版本是当前事务可见的。
creator-trx-id | trx-ids | up-limit-id 、low-limit-id |
---|---|---|
创建这个readview的事务id,只有对表中记录做改动时才会为事务分配事务id | 生成readview时当前系统中活跃的读写事务id列表 | 活跃事务的最小id 、生成readview系统中应该分配给下一个事务的id值 |
访问规则
- 如果被访问版本的trx-id属性值与readview中creator-trx-id相同,意味着当前事务在访问它自己修改过的记录,当前版本可以被当前事务访问
- 如果被访问版本的trx-id 小于up-limit-id ,表明生成该版本的事务在当前事务生成的readview前已经提交,该版本可以被当前事务访问
- 如果被被访问的tix-id大于或等于low-limit-id,表明生成该版本的事务在当前事务生成readview后才开启,所以便版本不可以。。
- 如果 。。介于up -low之间,判断trx-id是不是在trx-ids列表,如果在,说明该版本事务还是活跃的,不可以被访问;如果不在,说明已经提交,可以被访问。
操作流程
- 首先获取事务自己的版本号(事务id)
- 获取readview
- 查询得到的数据,然后与readview中的事务版本进行比较
- 如果不符合readview规则,就需要从undo log中获取历史快照,
- 最后返回符合规则的数据。
如果某个版本的数据对当前事务不可见时,就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,直到版本链中的最后一个版本,MVCC是通过undo log +readview进行数据读取,undo log 保存了历史快照,readview规则帮助判断当前版本的数据是否可见。当隔离级别为RC时,每次select查询都会重新获取一次readview;当为RR时,只在第一次select时获取readview ,后面的会复用这个readview。
如何解决幻读?
对于快照读: 核心在于readview原理:当隔离级别为RC时,每次select查询都会重新获取一次readview;当为RR时,只在第一次select时获取readview ,后面的会复用这个readview。
对于当前读: 加锁,间隙锁解决
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?