MVCC(Multi-Version Concurrency Control) 多版本并发控制
作用:在RC RR级别下,实现读写并发,读操作不受写的影响。
实现:innodb层面实现,通过undo log和可见性算法实现。
undo log 回滚日志版本链
当事务修改值后,引擎会记录一条undo log 用于事务失败时进行回滚操作;insert : delete。
每条记录还会附带事务id(trx_id),上级undo log指针(roll_pointer),形成一个版本链。
当事务提交后,若是新增直接清除,若是更新需要等到没有被其他事务查询,就可以清空undo log
read view一致性视图
在RC RR级别下,开启事务执行查询时,会生成一个read-view,由当前未提交的事务id列表和当前已提交的最大事务id组成;
read-view结构:[uncommitted_trx_id_list],committed_max_trx_id
RC级别中,每次查询都会产生一个新的read-view。
RR级别中,只有第一次查询会产生,直到提交结束。
可见性算法
- 执行sql,找到对应的数据row。
- 在undo log中找到该row对应的版本链,若row没有undo log,就不需要比较直接可见。
- row对应的undo log取最新的一条记录与read-view进行比较:row.trx_id
- 若比较后不可见,并且row还存在上级版本链,继续比对,一直重复,直到数据可见,或版本链结束。
//通过sql查询到数据row
row = do_sql();
//定义read_view,当前已提交的最大事务id,和所有未提交的事务id
//如果是RR级别,read_view生成一次后,不会变
//如果是RC级别,每次查询都会生成
read_view = {
"uncommitted_trx_id_list": [100,200,300],
"committed_max_trx_id": 500
}
//找到该数据行对应的undo链表
row_undo_list = find_undo_list(row);
//如果该数据不存在undo log,说明该数据没被修改过,不需要比较,直接返回最新的值
//当事务已经提交,并且没有被查询时,会被清空undo log
if(row_undo_list.isEmpty()){
return row;
}
//遍历数据行的undo链表
while(row_undo_list.hasNext()){
//从最新一条undo记录,逐行取出
row_undo = row_undo_list.next();
//比较是否符合read_view规则
if(row_undo.trx_id <= committed_max_trx_id
&& !read_view.uncommitted_trx_id_list.contains(row_undo.trx_id)){
//符合规则,返回undo对应的数据
return row_undo;
}
}
//如果没有符合read_view规则的,说明不可见
return null;