什么是MVCC?
一、背景
并发:在同一时间内,两个或多个任务交替执行(cpu的上下切换)。
高并发的时候,有时候会出现并发问题。所以就要并发控制。
说到并发控制,很多人第一反应都是加锁,确实,加锁也是解决并发问题最场景的方案。但是,除了加锁外,在数据库方面,有一种无锁的方案可以实现并发控制,那就是MVCC。
二、场景
案例一:订票系统,假设某航班只有一张票,如果有1w人来订票,如果解决这一张票的问题?
首先想到的并发的方案:
- 使用锁同步(synchronized关键字):一种是对象锁,用于线程同步;一种是数据库锁。
- 使用传统的物理锁:乐观锁或者悲观锁
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。. 因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。. 因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。
那么怎么使用乐观锁?
使用前提:在表中增加一个字段:version版本号 ,long类型。
原理:只有当前版本号等于数据库版本号才能提交修改,提交完后,版本号version++。
三、什么是MVCC?
MVCC:Multi Version Concurrency Control:多版本并发控制。是为了在读取数据时不加锁来提高读取效率和并发性的一种手段。主要针对的是读-写并发。
数据库并发有以下几种场景:
读-读:不存在任何问题。
读-写:有线程安全问题,可能出现脏读、幻读、不可重复读。
写-写:有线程安全问题,可能存在更新丢失等。
四、MVCC实现原理?
1.回顾事务的特性
原子性:通过undolog实现。
持久性:通过redolog实现。
隔离性:通过加锁(当前读)&MVCC(快照读)实现。
一致性:通过undolog、redolog、隔离性共同实现。
2.回顾事务的隔离级别
读未提交:允许读取尚未提交的数据变更。可能会导致脏读、幻读或不可重复读。
读已提交:允许读取已经提交的数据。可能会导致幻读和不可重复读。
可重复读:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改。可能会导致幻读。
可串行化:最高隔离级别。
3.再了解一些什么是快照读和当前读?
快照读:就是读取的是快照数据,像我们常用的普通select语句在不加锁的情况下就是快照读。
当前读:就是读取当前最新数据,像加锁的select语句。
可以说MVCC实现的基础就是快照读,而当前读是悲观锁的基础。
当前读的sql实现:
select lock in share mode(共享锁)
select for update(排他锁)
update(排他锁)
insert(排他锁)
delete(排他锁)
4.那么快照读 读取的是哪里的数据?
答案是:undo log日志。
undo log:存放事务没有提交前的数据,当事务异常回滚时,可以利用undo log进行回退。
5.那么一条记录在同一时刻可能有多个事务在执行,undo log就会有一条记录的多个快照,那么在这一时刻发送select语句进行快照读的时候,应该读哪个快照呢?
这就要靠mysql数据库中innodb引擎中提供的一些隐藏字段:分别是:row_id、trx_id和roll_pointer。
- row_id:隐藏主键,如果没有给表创建主键,那么就会用rowid字段来创建聚簇索引。
- trx_id:记录这条记录的最新的事务id。
- roll_pointer:记录这条记录的上一个版本地址(undo log中的上一个快照地址)。
还要靠Read View来解决可见性的问题。有几个重要属性:
- trx_ids:系统当前未提交的事务id的列表。
- low_limit_id:未提交的事务中最大的事务id。
- up_limit_id:未提交的事务中最小的事务id。
- creator_trx_id:执行select读这个操作的事务的id。
注意:每开启一个事务,都会从数据库中获取一个事务id,事务id是自增的。
6.那么一个事务应该看到哪些快照呢?
其实原则就是:事务id大的事务能看到事务id小的事务的变更结果。
例如当前有一个事务3想要对某条记录进行一次快照读,首先会创建一个read view,并把当前所有未提交的事务的信息记录下来,比如up_limit_id=2,low_limit_id=5,trx_ids=[2,4,5],creator_trx_id=6。
这条记录记录了上一次修改的事务id:trx_id=3。
接下来数据库会拿这条记录 trx_id=3和Read View比较。
- 如果trx_id=3=creator_trx_id=6,则说明这条记录对当前事务就是可见的。
- 如果trx_id=3<up_limit_id=2,则说明在read view中所有未提交的事务创建之前,trx_id这个事务就已经提交了。所以,这条记录对当前事务就是可见的。
- 如果trx_id=3>low_limit_id=6,则说明trx_id这个事务在read veiw中所有未提交事务创建之后才提交的。所以,这条记录对于当前事务就是不可见的。
- 如果up_limit_id=2<=trx_id=3<=low_limit_id=6,则说明trx_id这个事务在trx_ids列表中,对当前事务是不可见的。
当数据的事务id不符合read view的时候,那就需要从undo log里面获取数据的历史快照,然后数据快照的事务id在和read view做对比。

五、MVCC和隔离级别
根据不同的事务隔离级别,read view的获取是不同的。
- 在读已提交 事务隔离级别下,一个事务每一次select都会重新获取一次read view。
- 在可重复读 事务隔离级别下,一个事务只在第一次select的时候会获取一次read view。可以解决幻读问题。

浙公网安备 33010602011771号