我正在参加「掘金·启航计划」
MVCC (mysql的多版本控制器)
MVCC 是 InnoDB引擎中解决 隔离性问题而被提出的。主要用于控制 读-写时产生的并发问题的解决方案。MVCC 是一种 快照读-写并发时不加锁的解决方案。
快照读和当前读的区别
- 快照读:读取某一时刻的数据,即不加锁的非阻塞读,不一定是最新的数据。
- 当前读:读取程序最新的数据,当前读是一种加锁的操作,是悲观锁的实现。
- 注:在
串行化隔离级别下,快照读会退化成当前读
MVCC 实现方案(三个隐藏字段、undo log 、read View)
三个隐藏字段
MVCC 其实维护了 三个隐藏字段 隐藏主键,事务id,回滚指针,结构如下
- 虚拟主键id(DB_ROW_ID ):是默认维护的一个主键,如果一个表没有主键,会使用这个主键(为聚簇索引)
- 事务id(DB_TRX_ID):记录当前的事务
- 回滚指针(DB_ROLL_RTR):记录上一条事务版本的地址,用于连接多版本。
Undo log
InnoDB把这些为了回滚而记录的这些东西称之为undo log。undo log 里面记录了 insert、update 、deleted 的执行记录,用于数据恢复和备份。(记录的是数据修改之前的样子)
-
insert log
-
update log
-
delete log
- 删除操作只是把数据隐藏删除标志位
DELETE_BIT修改为true。不会真实的删除。 - 为了节省磁盘空间,InnoDB有专门的purge线程来清理DELETED_BIT为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中
最老活跃事务的read view(保证了数据是最新的));如果某个记录的DELETED_BIT为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的
- 删除操作只是把数据隐藏删除标志位
对 MVCC 实质上有帮助的就只有 update log。
undo log 的结构 实质就是一个链式结构
事务1 :事务1 可以看做一个update 记录,已经完成了一次修改。
事务2:事务2把 事务1 中的name修改成了 huangle ,然后通过回滚指针来关联事务1的数据。如果事务1的数据 删除标志位 被标志为 TREU 了,那么后面就会被删除。
Read View
read view 是 事务进行快照读的时候产生的一个读视图。在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
快照读是用来判断可见性的,事务通过产生的快照读作为条件来检查当前事务能够看见哪个版本的数据,可能是最新版本的数据,也可能是undo log 里面某个版本的数据。
read view 遵循一个可见性算法,主要是通过 获取要修改记录的 最新DB_TRX_ID(事务id)取出来,与其他正在活跃的事务id做比较。如果通过 比较,不符合可见性,就会遍历 undo log 的记录,找到一个符合的记录,这个记录就是当前事务 可见的版本数据。
可见性比较条件的源码
read view 维护三个全局变量
- trx_list (事务列表) 未提交的事务列表,用来维护 read view此刻整活跃的事务id
- up_limit_id trx_list中最小的事务id
- low_limit_id read view 生成时刻尚未分配的下一个事务id,就是当前最大的事务id +1
判断流程
- 首先比较DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到DB_TRX_ID 所在的记录,如果大于等于进入下一个判断
- 接下来判断 DB_TRX_ID 大于等于 low_limit_id , 如果大于等于则代表DB_TRX_ID 所在的记录在Read View生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断
- 判断DB_TRX_ID 是否在活跃事务之中,trx_list.contains(DB_TRX_ID),如果在,则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在Read View生成之前就已经Commit了,你修改的结果,我当前事务是能看见的
判断一个已经提交的事务是否对一个快照读可见,主要比较 3 步
- 1、看当前已经提交的事务id 是否 比 up_limit_id 还小,如果小 说明是 历史提交的,大于则继续判断。
- 2、看是否在当前正在活动的事务列表中,如果不在,继续判断。
- 3、判断当前事务是否比 最大的事务大,如果大,表示是之后产生的,不可见,如果小,表示是最新产生的,可见
总结就是 low_limite_id> DB_TRX_ID > up_limite_id & DB_TRX_ID not in TRX_LIST
总结
MVCC 通过隐藏字段来区分每一个版本的区别。然后通过 undo log 来记录每一个版本数据的关系。最后面通过 read view 快照视图来判断哪些版本可见,哪些版本不可见。从而解决数据的隔离性