MVCC-多版本并发控制
目录
什么是MVCC ?
MVCC关键知识点
隐式字段
事务ID
undo log
版本链
快照和当前读
Read View
MVCC实现原理
执行流程
MVCC实现"读已提交"
什么是MVCC ?
MVCC(Mutil-Version Concurrency Control),全称:多版本并发控制,这是一种并发事务环境下实现数据安全控制的方法,其本质上是一种乐观锁的实现。MySQL在InnoDB存储引擎中,通过MVCC实现读已提交(READ COMMITTD)和可重复读(REPEATABLE READ)这两种隔离级别。相对于加锁,MVCC用更好的方式去处理读写冲突,有效提高数据库并发性能。
MVCC关键知识点
MVCC的实现,主要通过事务ID、undolog 日志、版本链。
隐式字段
InnoDB存储引擎,每一行记录都有两个隐藏列trx_id、roll_pointer,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列row_id。
事务ID
事务每次开启前,都会从数据库获得一个事务ID:trx_id,该值属于自增长,每进行一次事务操作,就会自增1。可以从事务ID判断事务的执行先后顺序。
undo log
undo log,回滚日志,用于记录数据被修改前的信息。在表记录修改之前,会先把数据拷贝到undo log里,如果事务回滚,即可以通过undo log来还原数据。也可以用于用于MVCC快照读。
版本链
多个事务并行操作某一行数据时,不同事务对该行数据的修改会产生多个版本,然后通过回滚指针(roll_pointer),连成一个链表,这个链表就称为版本链。
假设现在有一张core_user表,表里面有一条数据,id为1,名字为孙权:
现在开启一个事务A:对core_user表执行update core_user set name ="曹操" where id=1,会进行如下流程操作
- 首先获得一个事务ID=100
- 把core_user表修改前的数据,拷贝到undo log
- 修改core_user表中,id=1的数据,名字改为曹操
- 把修改后的数据事务Id=101改成当前事务版本号,并把roll_pointer指向undo log数据地址。
快照和当前读
快照读
读取的是记录数据的可见版本(有旧的版本)。不加锁,普通的select语句都是快照读。
例如:
select * from core_user where id > 2;
当前读
读取的是记录数据的最新版本,显式加锁的都是当前读。
例如:
select * from core_user where id > 2 for update;
select * from account where id > 2 lock in share mode;
Read View
事务在执行SQL时,会产生一个ReadView数据快照,用于判断当前事务可见哪个版本的数据。
ReadView中保存了四个重要字段:
- m_ids:通过一个List数据结构,保存未提交的活跃读写事务ID。
- min_limit_id:表示生成Read View时,活跃读写事务中最小的事务id,即m_ids中的最小值。
- max_limit_id:表示生成Read View时,系统中应该分配给下一个事务的id值。
- creator_trx_id: 创建当前Read View的事务ID。
可见性判断规则:
当事务查询数据时,会从版本链的最新版本开始,通过 Read View 判断该版本是否可见。若不可见,则沿DB_ROLL_PTR回溯到上一个版本,重复判断,直到找到可见版本或版本链结束(返回空)。
对于版本链中的某个版本( DB_TRX_ID 为trx_id),当前事务能否看到它,取决于:
- 若trx_id == creator_trx_id:该版本由当前事务修改,可见;
- 若trx_id < min_trx_id:修改该版本的事务在当前事务生成 Read View 前已提交,可见;
- 若trx_id > max_trx_id:修改该版本的事务在当前事务生成Read View 后才启动,不可见;
- 若min_trx_id ≤ trx_id ≤ max_trx_id:
若trx_id在m_ids中(即修改事务仍活跃),不可见;
若trx_id不在m_ids中(即修改事务已提交),可见。
MVCC实现原理
执行流程
- 获取事务的版本号,即事务ID
- 获取Read View
- 查询得到的数据,然后Read View中的事务版本号进行比较。
- 如果不符合Read View的可见性规则, 即就需要Undo log中历史快照;
- 最后返回符合规则的数据
所以,InnoDB 实现MVCC,是通过Read View+ Undo Log 实现的,Undo Log 保存了历史快照,Read View可见性规则帮助判断当前版本的数据是否可见。
MVCC实现"读已提交"
在读已提交的事务隔离级别下,仍然存在不可重复读的事务并发问题。
例如:core_user表中,已存在如下数据,事务隔离级别设置为读已提交(RC),事务A和事务B同时对core_user表进行查询和修改操作。
最终,事务A查询到的结果是,name=曹操的记录,发生不可重复读的事务并发问题。
MVCC的执行流程如下:
- A开启事务,首先得到一个事务ID为100:trx_id=100
- B开启事务,得到事务ID为101:trx_id=101
- 事务A和事务B,会各自生成一个Read View
- 事务A第一次查询时,通过trx_id=100,查到name=孙权
- 事务B进行修改操作,把名字改为曹操。把原数据拷贝到undo log,然后对数据进行修改,标记事务ID和上一个数据版本在undo log的地址。
- 事务A再次执行查询操作,会新生成一个Read View
- 回到版本链中,查找到最新版本的列name的内容是:曹操该版本的trx_id值为101