1、mvcc是什么
mvcc全称是MultiVersion Concurrency Control,即多版本并发控制 。
InnoDB中mvcc的实现,是通过在undolog中维护一个数据的多个版本,然后根据隔离级别和用户读请求(如当前读或快照读)的不同,返回不同版本的数据给用户。
2、mvcc的作用
在数据库发展的前期,只有读读之间可以并发。读写、写读、写写都要阻塞。引入mvcc之后,只有写写之间相互阻塞,其他三种操作都可以并行。很好的解决了数据并发读写问题,提供InnoDB的并发度。
3、事务隔离级别
1)事务隔离级别
read uncommitted:简称RU, 即读未提交,最低级别最低,一个事务运行过程中会读取到其他事务未提交的数据,因此会出现脏读,脏读同时也是不可重复读
read committed:简称RC,即读已提交,一个事务运行过程中可以读取到其他事务已提交的数据,因此会出现不可重复读问题
repeatable-read:简称RR, 即可重复读,一个事务运行过程中执行相同SQL,每次可以读取到相同结果集,不管其他事务是否提交,解决了部分?幻读问题。MySQL默认的隔离级别
serializable:即: 串行化,事务排队,隔离级别最高,并发性能最差,基本不会去使用。
2)不同事务隔离级别下可能存在的问题
说明:
- 以上四种隔离级别,从上到下,隔离级别越来越高,但并发性越来越低。
- 幻读的解决不是因为mvcc,而是因为间隙锁。 间隙锁适用于rr以上的隔离级别。
3)快照读在rc与rr隔离级别下的生成时机
rc隔离级别下,每个快照读都会生成并获取最新的Read View,所以在rc级别下的事务中可以看到其他会话下事务提交。
rr隔离级别下,同一个事务中的只有第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View,之后的查询就不会重复生成了,所以一个事务的查询结果每次都是一样的。
4、快照读与当前读
1. 快照读
快照读也叫一致性非锁定读。它是该事务在执行快照读的那一刻,生成数据库系统的快照。
快照读/一致性非锁定读:读取行记录的历史版本。像 普通的查询(select)属于非锁定读,不会添加任何锁。它读取的是行记录的历史版本(至于读取行记录的哪一个历史版本,通过比较事务ID DB_TRX_ID),因为历史数据不会被修改,相当于静态数据,不会加锁,可重复读。
2.当前读
当前读也叫一致性锁定读。当有人跟你提到这两个术语时,你应当快速想到,两者是等价的。下面我们介绍下,当前读/一致性锁定读的定义。
当前读/一致性锁定读: 读取行记录当前的最新版本,有可能会加锁,防止数据被修改。常见属于当前读的语句:
1)select …. for share #mysql8.0 新增的方式。
2)select … lock in shard mode # 添加s锁,其他事务可以读,但是修改会被阻塞。
3)select … for update #添加X锁,其他事务可以读,但是修改操作或select … for udapte操作会被阻塞
4)insert/update/delete
5、mvcc的内部实现
InnoDB是通过在undolog中维护一个行数据的多个版本,不同版本之间通过 回滚指针(DB_ROLL_PTR列)串连起来,形成一个链表,来实现mvcc。
然后根据隔离级别和用户读请求(如当前读或快照读)的不同,返回不同版本的数据给用户。也可以在回滚的时候覆盖数据页上的数据。
在InnoDB内部中,会记录一个全局的活跃读写事务数组(read view),其主要用来判断事务的可见性。
InnoDB行记录结构:
InnoDB表的每行记录都有三个隐藏字段。
(1)DB_TRX_ID字段,6字节。表示插入或更新行的最后一个事务的事务标识符(最近修改的事务ID)。此外,删除在内部被视为更新,其中行中的特殊位被设置为将其标记为已删除。
(2)DB_ROLL_PTR字段,7字节,叫做回滚指针(roll pointer)。每行的回滚指针指向当前行记录的上一个版本,回滚指针指向写入回滚段的撤消日志(Undo Log)。如果行已更新,则撤消日志包含重建更新前该行内容所需的信息。
(3)DB_ROW_ID字段,6字节。隐藏主键,包含一个随着新行插入而单调增加的行ID,如果innodb自动生成聚集索引,则该索引包含行ID值。否则,DB_ROW_ID列不会出现在任何索引中。
6、mvcc的产生过程
下面我们两个示例来解释行记录多版本的产生过程,方便大家更好的理解。
示例1:新增一条记录,并对其进行1次修改
1.当插入一条新数据时,记录上对应的回滚段指针为NUL
语句: insert into tb01(id,c1,c2) values(1,'a1','a2');
这个过程,做了这样几件事:
1) 用排他锁锁定该行;
2) 把该行修改前的值拷贝到Undo Log中
3) 修改当前行的值
4)填写事务编号,使回滚指针指向Undo Log中的修改前的行。因为当前是插入一条新数据,此记录回滚指针指向NULL(空)
5) 记录Redo Log,包括Undo Log中的变化
说明:这里不难理解,因为是插入一条新数据,所以这条新数据,不存在对应行记录的历史版本,即此行数据的回滚指针指向NULL(空)
2.对记录做第一次修改
语句:update tb01 set c1='a11' where id=1;
多次更新后,回滚指针会把不同版本的记录串在一起,形成链表 。在InnoDB中存在purge线程,它会查询那些比现在最老的活动事务还早的Undo Log,并删除它们,从而保证Undo Log文件不至于无限增长。
7、事务可见性判断
当事务在进行快照读的时候,会生成一个read view(读视图),来记录系统当前活跃事务(活跃事务上尚未提交的事务)的ID(id是单调递增的,越新的事务id值越大)。然后结合可见性算法,来判断当前事务对其他正在运行的事务是否可见。
- read view包含的字段:
trx_ids: 当前系统活跃(未提交)的事务id集合。
up_limit_id: read view活跃事务列表中最小的id值
low_limit_id: read view活跃事务列表中最大的id值,当前系统尚未分配的下一个事务id值得
creator_trx_id: 创建当前read view的事务版本号;
- read view 可见性判断条件
1)如果数据的事务id小于read view中的最小活跃事务ID,即 db_trx_id < up_limit_id,则说明该数据在当前事务启动之前就已经存在了。因此当前事务能看到该db_trx_id所在的记录。
或者
如果数据的事务id等于read view 中的creator_trx_id ,即db_trx_id == creator_trx_id,则说明这个数据是当前事务自己生成的,当前事务自然可以看到该数据。
这俩条件如果都不满足,则进入下一个判断。
2)如果数据的事务id大于等于read view 中的当前系统的最大事务ID,即db_trx_id >= low_limit_id,则说明该数据是在当前read view 创建之后才产生的,那么该数据对当前事务是不可见的。如果小于,则进入下一个判断。
3)判断数据的事务id(db_trx_id)是否在read view的活跃事务中(trx_ids)。如果在,就说明当前事务的read view在生成的时候,该数据的事务还是活跃的,还没有提交(commit) , 因此该数据对当前事务是不可见的。如果不在,就说明,当前事务的read view在生成的时候,该数据的事务已经提交(commit) ,因此该数据对当前事务是可见的。
如有错误,还请多多指正。关于innodb mvcc,如果您有疑问,可以在下方评论区留言,谢谢