MVCC 机制的原理及实现
时间:2025-11-04 00:16:12 出处:系统运维阅读(143)
什么是机制 MVCC
MVCC(Multiversion Concurrency Control)翻译过来是多版本并发控制,和数据库锁一样,理及也是实现一种并发控制的解决方案。
在InnoDB中的机制实现主要是为了提高数据库并发性能,用更好的理及方式去处理读-写冲突,做到即使有读写冲突时,实现也能做到不加锁,机制非阻塞并发读,理及而这个读指的实现就是快照读,而非当前读。机制当前读实际上是理及一种加锁的操作,是实现悲观锁的实现。而MVCC本质是机制采用乐观锁思想的一种方式。
快照读所谓快照读,理及就是实现读取的是快照数据,即快照生成的服务器托管那一刻的数据,像我们常用的普通的SELECT语句在不加锁情况下就是快照读:
复制SELECT * FROM xx_table WHERE ...1.注意:快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。
当前读当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的SELECT,或者对数据进行增删改都会进行当前读:
复制SELECT * FROM xx_table LOCK IN SHARE MODE; #共享锁 SELECT * FROM xx_table FOR UPDATE; #排他锁 INSERT INTO xx_table values ... #排他锁 DELETE FROM xx_table WHERE ... #排他锁 UPDATE xx_table SET ... #排他锁1.2.3.4.5. 解决什么问题我们知道,在数据库中,对数据的操作主要有2种,分别是读和写,而在并发场景下,就可能出现以下三种情况:
读-读并发:不存在任何问题,也不需要并发控制读-写并发:有线程安全问题,可能会造成事务隔离性问题,云服务器可能遇到脏读,幻读,不可重复读写-写并发:有线程安全问题,可能会存在更新丢失问题在没有写的情况下读-读并发是不会出现问题的,而写-写并发这种情况比较常用的就是通过加锁的方式实现。那么,读-写并发则可以通过MVCC的机制解决。
实现原理
Undo Logundo log是Mysql中比较重要的事务日志之一,是一种用于回退的日志,在事务没提交之前,MySQL会先记录更新前的数据到undo log日志文件里面,当事务回滚时或者数据库崩溃时,可以利用undo log来进行回退。
insert undo只在事务回滚时起作用,当事务提交后,该类型的undo日志就没用了,它占用的Undo Log Segment也会被系统回收update或delete时产生的undo log,不仅在事务回滚时需要,源码下载在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除一条记录在同一时刻可能有多个事务在执行,那么undo log会有一条记录的多个快照,那么在这一时刻发生SELECT要进行快照读的时候,要读哪个快照呢?
行记录的隐式字段其实,数据库中的每行记录中,除了保存了我们自己定义的一些字段以外,还有一些重要的隐式字段的:
db_row_id:隐藏主键,如果我们没有给这个表创建主键,那么会以这个字段来创建聚簇索引db_trx_id:对这条记录做了最新一次修改的事务的IDdb_roll_ptr:回滚指针,指向这条记录的上一个版本,其实他指向的就是Undo Log中的上一个版本的快照的地址注意:以上字段只有在聚簇索引的行记录中才会有,而在普通二级索引中是没有这些值的。
每一次记录变更之前都会先存储一份快照到undo log中,那么这几个隐式字段也会跟着记录一起保存在undo log中,就这样,每一个快照中都有一个db_trx_id字段表示了对这个记录做了最新一次修改的事务的ID ,以及一个db_roll_ptr字段指向了上一个快照的地址。(db_trx_id和db_roll_ptr是重点,后面还会用到)
这样就形成了一个快照链表:
图片
有了undo log,又有了几个隐式字段,我们好像还是不知道具体应该读取哪个快照,那怎么办呢?
Read ViewRead View 是InnoDB中一个至关重要的概念,是实现MVCC的基础,同时也是支持不同的事务隔离级别的基础,同时提高系统的并发能力和性能。
Read View主要来帮我们解决可见性的问题的, 即他会来告诉我们本次事务应该看到哪个快照,不应该看到哪个快照。
在可重复读(Repeatable Read)级别下,快照(Read View)在事务开始后第一次查询时创建一次,并在整个事务期间保持不变。在读已提交(Read Committed)级别下,快照(Read View)会在每次查询时重新创建,以反映数据库中的最新提交更改。在Read View中有几个重要的属性:
trx_ids,表示在生成Read View时当前系统中活跃的读写事务的事务id列表。low_limit_id,应该分配给下一个事务的id值。up_limit_id,未提交的事务中最小的事务ID。creator_trx_id,创建这个Read View的事务ID。Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID )取出来,与系统当前其他活跃事务的ID去对比(由Read View 维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID,那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本。
案例假如一个ReadView的内容为:
复制trx_ids = [5,6,8) low_limit_id = 8 up_limit_id = 5 creator_trx_id = 71.2.3.4.假设当前事务要读取某一个记录行,该记录行的db_trx_id(即最新修改该行的事务ID)为 trx_id,那么,就有以下几种情况了:
1、trx_id<up_limit_id,即小于5的事务,说明这些事务在生成ReadView之前就已经提交了,那么该事务的结果就是可见的。
2、trx_id>=low_limit_id,即大于8的事务,说明该事务在生成ReadView后才生成,所以该事务的结果就是不可见的。
3、up_limit_id<trx_id<low_limit_id,即大于等于5,小于8,这种情况下会再拿事务ID和Read View中的trx_ids进行逐一比较。
如果,事务ID在trx_ids列表中,如6,那么表示在当前事务开启时,这个事务还是活跃的,那么这个记录对于当前事务来说应该是不可见的。
如果,事务id不在trx_ids列表中,如7,那么表示的是在当前事务开启之前,其他事务对数据进行修改并提交了,所以,这条记录对当前事务就应该是可见的。
当然这里有个例外情况,那就是这个trx_id=creator_trx_id,那么就肯定是可见的
总结一下就是,一个事务能看到的是在他开始之前就已经提交的事务的结果,而未提交的结果都是不可见的。
当数据的事务ID不符合Read View规则时候,那就需要从undo log里面获取数据的历史快照,然后数据快照的事务ID再来和Read View进行可见性比较,如果找到一条快照,则返回,找不到则返回空。
总结
图片
在InnoDB中MVCC就是通过Read View + Undo Log来实现的,undo log中保存了历史快照,而Read View用来判断具体哪一个快照是可见的。
猜你喜欢
- 三星卡刷XP教程(详细步骤教你如何在三星手机上刷入XP系统,享受原汁原味的XP体验)
- Win8系统安装教程(轻松教你装Win8,让电脑焕然一新!)
- 重温经典,探索前端iPodNano6的魅力(一款小巧时尚的音乐伴侣,iPodNano6再度引领潮流)
- 体验beatsSolo3耳机带来的震撼音质(感受beatsSolo3耳机的卓越品质与出色性能)
- 电脑QQ时钟错误的原因和解决方法(探究电脑QQ时钟错误的根源,以及如何解决这一问题)
- 苹果系统装饰教程(简单易懂的教程帮你定制独一无二的苹果系统界面)
- 三星笔记本系统安装教程(一步步教你如何在三星笔记本上安装新系统,享受更流畅的使用体验)
- Win10快速启动功能(探索Win10快速启动功能的使用方法及优势)
- 如何关闭AirPlay镜像?(简易指南,告诉你如何正确关闭AirPlay镜像)