什么是mvcc
mvcc,全称Multi-Version Concurrency Control,即多版本并发控制。现代数据库(mysql,Oracle,PostgreSQL 等等)引擎采用的解决数据库读写冲突时的手段,目的在于提高数据库高并发场景下的吞吐性能。
不同事务在并发过程中,SELECT操作可以在不加锁的情况下通过MVCC机制查询指定的版本历史记录
举个例子
在事务 A 提交前后,事务 B 读取到的 x 的值是什么呢?
答案是:事务 B 在不同的隔离级别下,读取到的值不一样。
如果事务 B 的隔离级别是读未提交(RU),那么两次读取均读取到 x 的最新值,即 20。
如果事务 B 的隔离级别是读已提交(RC),那么第一次读取到旧值 10,第二次因为事务 A 已经提交,则读取到新值 20。
如果事务 B 的隔离级别是可重复读或者串行(RR,S),则两次均读到旧值 10,不论事务 A 是否已经提交。
可见在不同的隔离级别下,数据库通过 MVCC 和隔离级别,让事务之间并行操作遵循了某种规则,来保证单个事务内前后数据的一致性。
注意一个概念:MVCC并不是事务的隔离级别,实现隔离级别的一种技术,保留数据库中数据的多个版本来支持并发事务的执行
事务版本号
事务每次开启前,都会从数据库获得一个自增长的事务id,从事务ID中判断执行的先后顺序。
隐式字段
mysql举例:InnoDB是mysql存储引擎之一,每一行记录都有两个隐藏列trx_id、roll_pointer,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列row_id。
undo log
回滚日志,主要是记录数据被修改之前的信息,在表记录修改前,会先把数据拷贝到undo log中,如果事务回滚,可以通过undo log还原数据。
可以这样认为,当delete一条记录时,undo log 中会记录一条对应的insert记录,当update一条记录时,它记录一条对应相反的update记录。
undo log有什么用途呢?
-
事务回滚时,保证原子性和一致性。
-
用于MVCC快照读
版本链
多个事务并行操作一行数据时,不同事务对改行的数据的修改会产生多个版本,然后通过回滚指针(roll_pointer),连成一个链表,称为版本链
通过版本链,我们可以看出事务版本号、表格隐藏的列和undo log之间的关系,分析一下
假设现在有一张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数据地址。
mvcc两种读取的两种方式:当前读、快照读
快照读:事务在读取数据中获取了一个确定时间点的数据快照,该快照在整个事务执行过程中保持一致性,事务将看到事务开始时的执行状态。即使其他事务更改了数据,该快照也只能看到事务开始时的数据视图。
不加锁的select,就是快照读;
为什么出现快照读?
为了并发考虑的,不能每次都加锁,提高并发性能,快照读是基于多版本并发控制
存在问题
基于多版本,读到的不一定是数据的最新版本,可能是之前的历史版本
串行级的快照读会退化为当前读
当前读:读取数据最新提交的数据,数据库的最新状态,读取时要保证其他事务不能修改当前记录,对读取的数据加锁
共享锁:select lock in share mode
排它锁:select for update 、update、 insert 、delete
RC、RR级别下的InnoDB快照读有什么不同,因为Read View生成时机的不同,从而造成RC、RR级别下快照读的结果的不同
在RC级别下,事务中,每次快照读都会新生成一个快照和Read View,这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因
在RR级别下,某个事务的对某条记录的第一次快照读会创建一个快照(Read View),将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,之后的修改对其不可见
总结:在RC隔离级别下,是每个快照读都会生成并获取最新的Read View,而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View,之后的快照读获取的都是同一个Read View.
RR级别下怎么避免幻读
- 快照读,和避免不可重复读原理一样,可以避免幻读
- 当前读,因为每次都是读取新的快照,如果需要避免,可以通过加锁
限制新增或删除相同条件的数据