MVCC(Multi Version Concurrency Control)是在并发访问数据库是,通过对数据做多版本控制,避免因为写数据是要加写锁而阻塞读取请求,造成写数据无法读取数据的问题。
通俗的将就是MVCC通过保存数据的历史版本,根据对比数据的版本号来决定数据是否显示,在不需要加读锁的情况能打到事务的隔离效果,可以同时进行数据的读取和修改,极大地提升了事务的并发性能
包括下面的四个重要属性
每次事务开启前都会从数据库获取一个自增加的事务ID,可以从事务ID判断事物的执行先后顺序
Undo log 主要用于记录数据被修改之前的日志,在表之前的数据信息,在表信息修改之前会把数据存储到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
会进行如下操作
执行完成后的效果如图
使用innodb开启事务前会创建read_view,副本主要保存当前数据库系统中正处于活跃(没有commit)事务的ID号
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()