MVCC
2022/7/24 23:24:40
本文主要是介绍MVCC,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
MVCC
MVCC(Multi Version Concurrency Control)是在并发访问数据库是,通过对数据做多版本控制,避免因为写数据是要加写锁而阻塞读取请求,造成写数据无法读取数据的问题。
通俗的将就是MVCC通过保存数据的历史版本,根据对比数据的版本号来决定数据是否显示,在不需要加读锁的情况能打到事务的隔离效果,可以同时进行数据的读取和修改,极大地提升了事务的并发性能
Innodb的MVCC实现的核心点
包括下面的四个重要属性
- 事务版本号
- 表的隐藏列
- undo log
- read view
事务版本号
每次事务开启前都会从数据库获取一个自增加的事务ID,可以从事务ID判断事物的执行先后顺序
表格的隐藏列
- DB_TRX_ID 记录该数据事务的事务ID
- DB_ROLL_PTR 指向上一个版本的数据在undo log 里的位置指针
- DB_ROW_ID 隐藏ID,当创建表没有合适的索引作为聚集索引时,会用该隐藏ID创建聚集索引
Undo log
Undo log 主要用于记录数据被修改之前的日志,在表之前的数据信息,在表信息修改之前会把数据存储到undo log里,当事务进行回滚时可以通过undo log里的日志进行数据还原。
- 保证事务进行rollback时的原子性和一致性,当事务进行回滚的时候可以用undo log进行回复
- 用于MVCC快照读的数据,在MVCC多版本控制中,通过读取undo log的历史版本数据可以实现不同事务版本号都用友自己独立的快照数据版本
事务版本号、表格的隐藏列、undo log的关系
模拟一行数据修改的过程来了解十五号、表格隐藏的列和undo log他们之间的关系
准备一张原始的数据表
id | name | DB_TRX_ID | DB_ROLL_PTR |
---|---|---|---|
1 | 张三 | 103 | none |
开启事务
对user_info表执行 update user_info set name = '李四' where id = 1
会进行如下操作
- 获取到一个事务编号104
- 把user_info表修改前的数据拷贝到undo log
- 修改user_info表id=1的数据
- 把修改后的数据事务版本号改成当前事务版本号,并把DB_ROLL_PTR地址指向undo log数据地址
执行完成后的效果如图
Read View
使用innodb开启事务前会创建read_view,副本主要保存当前数据库系统中正处于活跃(没有commit)事务的ID号
read view的几个重要属性
- trx_ids:当前系统活跃(未提交)事务版本号集合
- low_limit_id:创建当前read veiw 时 当前系统最大事务版本号+1
- up_limit_id:创建当前read view 时系统正处于活跃事务的最小版本号
- creator_trx_id 创建当前read view的事务版本号
read view 匹配条件
- 数据事务ID < up_limit_id 则显示
- 数据事务ID>=low_limit_id则不显示,如果数据事务ID大于read view 中当前系统最大的事务ID,则说明该数据是在当前read view创建之后才产生的,所以数据不予显示
- up_limit_id<=数据事务ID<low_limit_id时,则需要具体看对应的事务id
- 如果事务不存在与trx_ids集合(说明read view产生的事务已经commit),则可以可以显示
- 事务ID存在trx_ids则说明read view 产生的数据还没有提交,如果数据的事务等于creator_trx_id,那么说明这个数据就是事务自己生成的,自己生成的数据自己当然能看见,所以这种情况此数据也是也可以显示的
- 如果事务id既存在于trx_ids而且有不等于creator_trx_id则说明read view产生的时候数据则提交,又不是自己生成的,这种情况不能显示。
- 查询时不满足read view 时,则从undo log里面获取数据,当数据事务id不满足read view条件时,查找对应的undo log,再执行1-3步
innodb实现mvcc的原理
- 获取事务版本号
- 获取一个read view
- 查询到数据,与read view事务版本号进行匹配
- 不符合read view规则的从undo log里获取历史版本数据
- 返回符合规则的数据
模拟两个线程分别进行更新和插叙你的情况
各个事务隔离级别下的read view 工作方式
RC(read commit)级别下同一个事务里面的每一次查询都会闯将新的read view,这样就可能造成同一个事务里前后读取的数据可能不一致(重复读),RR(repeat read)级别下一个事务里只会获取到一次read view副本,从而保证查询的数据都是一样的
read uncommited 级别的事务不会获取read view副本
快照度和当前度
快照读
快照读是指读取数据时不时读取最新版本的数据,而是基于历史版本读取一个快照信息
当前读
当前读是读取的数据库最新的数据,当前读和快照读不通,因为要读取最新的数据而且要保证事务的隔离性,所以当前读是需要对数据进行加锁的,使用for update
实例python代码
mvcc简单的实现
import copy global_trx_id = 0 datas = [] trx_ids = set() class Row: def __init__(self, _id, name, DB_TRX_ID, DB_ROLL_PTR): self.id = _id self.name = name self.DB_TRX_ID = DB_TRX_ID # 记录操作该数据事务的事务ID self.DB_ROLL_PTR = DB_ROLL_PTR # 指向上一个版本数据在undo log 里的位置指针 def __repr__(self): return "Row<id:{},name:{},DB_TRX_ID:{},DB_ROLL_PTR:{}>".format(self.id , self.name, self.DB_TRX_ID, self.DB_ROLL_PTR) class ReadView: def __init__(self, low_limit_id, up_limit_id, creator_trx_id, ): self.low_limit_id = low_limit_id # 创建当前read view 时“当前系统最大事务版本号+1” self.up_limit_id = up_limit_id # 创建当前read view 时“系统正 处于活跃的事务 最小版本号” self.creator_trx_id = creator_trx_id # 创建当前read view的事务版本号 def begin(row: Row): global global_trx_id cur_trx_id = global_trx_id trx_ids.add(cur_trx_id) global_trx_id += 1 return cur_trx_id def commit(trx_id): trx_ids.remove(trx_id) def update_data_name(cur_trx_id, row, name): # cur_trx_id = begin(row) row_log = copy.deepcopy(row) row.name = name row.DB_TRX_ID = cur_trx_id row.DB_ROLL_PTR = row_log def select_data_by_id(trx_id, id): read_view = get_read_view(trx_id) def match_rec(read_view, d): if d == None: return None if d.DB_TRX_ID < read_view.up_limit_id: return d else: if d.DB_TRX_ID not in trx_ids or (d.DB_TRX_ID in trx_ids and d.DB_TRX_ID == read_view.creator_trx_id): return d return match_rec(read_view, d.DB_ROLL_PTR) for d in datas: if d.id == id: # 开始进行事务的对比 return match_rec(read_view, d) return None def get_read_view(trx_id): rv = ReadView(global_trx_id, min(trx_ids), trx_id) return rv def main(): # update thread one_row = Row(1, "Foo", 99, None) datas.append(one_row) global global_trx_id global_trx_id = 102 # update begin update_trx_id = begin(one_row) # select begin select_trx_id = begin(one_row) # update data update_data_name(update_trx_id, one_row, "Bar") # select get view read_view = get_read_view(select_trx_id) # select found rec select_get_rec = select_data_by_id(select_trx_id, 1) print(select_get_rec) if __name__ == '__main__': main()
引用
- https://zhuanlan.zhihu.com/p/52977862
这篇关于MVCC的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-25【机器学习(二)】分类和回归任务-决策树(Decision Tree,DT)算法-Sentosa_DSML社区版
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享