什么是MVCC
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
我们知道,一般情况下我们使用mysql数据库的时候使用的是InnoDB引擎,InnoDB存储引擎是 支持事务的,那么当多线程同时执行事务的时候,可能会出现并发问题。这个 时候需要一个能够控制并发的方法,MVCC就起到了这个作用。
Mysql的undo log
MVCC底层以来mysql的undo log,undo log记录了数据库的操作,因为undo log是逻辑日志,可以理解为delete一条记录的时候,undo log会记录一条对应的insert记录,update一条记录的时候,undo log会记录一条相反的update记录,当事务失败需要回滚操作时,就可以通过读取undo log中响应的内容进行回滚,MVCC就利用到了undo log
MVCC实现原理
MVCC的实现,利用到了数据库的隐式字段,undo log和ReadView。首先来看隐式字段,起始mysql在表中的每行记录的后面,都隐式的记录了DB_TRX_ID(最近修改(修改/插入))事务ID),DB_ROLL_PTR(回滚指针,指向这条记录的上 一个版本),DB_ROW_ID(自增ID,如果数据表没有主键,则默认以此ID简历聚簇索引)着几个隐式字段。
undo log分为两种:
- 分别为insert undo log,在insert新纪录时产生的undo log,只在事务回滚时需要,并且在事务提交后可以被立即丢弃。
- 还有update undo log,事务在进行update或delete时产生的undo log,不仅在事务回滚时需要,在快照读时也需要,所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会purge线程统一清除。MVCC利用到的时update undo log。
实际上undo log记录的是一个版本链,假设数据库中有一条记录如下:
现在有一个事务A修改了这条记录,把name改为tom,这个时候的操作流程为:
- 事务A首先对该行记录加上行锁
- 然后将该行记录拷贝到undo log中,作为一个旧的版本
- 拷贝完之后将改行name改为tom,然后将改行的DB_TRX_ID的值盖屋事务A的ID,此时假设事务A的ID为1,将该行的DB_POLL_PTR指向拷贝到undo log的那条记录
- 事务提交后,释放锁
此时的情况如下:
此时又有一个事务B来修改这条记录,把age改为28,这时候的操作流程如下:
- 事务B对该行记录加上行锁
- 将改行记录拷贝到undo log中,作为一个旧的版本,此时发现undo log已经有记录了,那么新的一条undo log作为链表的表头插入到该行记录的undo log的最前面
- 拷贝完后将该行的age改为28,然后将改行的DB_TRX_ID的值改为事务B的ID,此时假设事务B的ID为2,将该行的DB_POLL_PRT指向拷贝到undo log的那条记录
- 事务提交后释放锁
此时的情况如下:
从上面我们可以看到,不同的事务或者相同的事务对同一行记录进行修改,会使得该行记录的 undo log形成一个版本链,undo log的链首就是最近一次的旧记录,而链尾就是最早一次的旧记录。
现在我们来假设一种情况,先假设事务A和事务B都没有提交,这时候有一个事务C,修改了name为tome的记录,把age改成了30,然后把事务提交,事务C的ID为3,同样的,会插入一条记录到undo log,此时的undo log版本链链首记录的DB_TRX_ID为3.
现在有一个事务D,查询name为tom的记录,此时就会启用快照读,快照是事务开始由查询操作触发的一个数据快照,不加锁的读在可重复读隔离级别下默认就是快照读,相对于快照读还有一个叫当前读,更新操作都是当前读。在快照读时会产生一个读视图(Read view),在该事务执行快照读的那一刻,会产生数据库当前的一个快照,记录并且维护当前活跃的事务ID,因为事务的ID都是自增的,所以越新的事务ID越大。读试图遵循可见性算法,而是否可见则需要一些判断,读试图中除了记录当前活跃的事务ID以外,还记录了当前创建的最大事务ID,快照读时需要和Read view作比较来获得可见性结果。
Read view主要时把当前事务的ID和系统中的活跃事务的ID作比较,比较的规则如下:
首先,Read view中会有一个Read view生成时刻系统中活跃的事务ID的数组,暂称为id_list
然后Read view中会记录一个id_list中最小的事务ID,暂称为low_id
最后Read view中还会记录一个Read view生成时刻系统中尚未分配的事务ID,也就是当前最大的事务ID+1,暂称为high_id
- 当前事务ID如果小于low_id,则当前事务可见
- 当前事务ID如果大于high_id,则当前事务不可见
- 当前事务大于low_id小于high_id,再判断是否在id_list中,如果在,说明活跃的事务还没提交,当前事务不可见,但是对于活跃的事务本身可见;如果不再id_list中,则当前事务可见
如果可见性结果为不可见的话,需要通过DB_ROLL_PTR到undo log中取出该记录的DB_TRX_ID进行比较,通过遍历版本链,直到找到满足特定条件的DB_TRX_ID,那么这个DB_TRX_ID所在的旧记录就是当前事务能看到的最新老版本。
参考地址:
https://www.jb51.net/article/210018.htm
https://blog.csdn.net/DILIGENT203/article/details/100751755