目录
一、思考
1、事务是什么?为什么要有事务?事务的4个基本要素是什么?并发时会有什么问题?
2、由于并发时会存在问题,不能保证事务的4个基本要素,所以有了四个隔离级别。
3、但是四个隔离界别是怎么实现的呢?可以通过锁来实现,但是加锁会导致数据库性能的降低(读数据时,没办法修改,修改数据时,没办法读取)。那怎么办?能不能不加锁?
答案就是MVCC(多版本并发控制)通过对数据做多版本管理,通过保存数据的历史版本,根据比较数据的版本号来决定数据的是否显示,避免加写锁而导致阻塞,造成写数据时无法读取数据的问题。
二、原理
1、了解原理前,得知道mysql中什么是快照读?什么是当前读?
- 快照读:读取的是快照数据,不加锁的简单 Select 都属于快照读
SELECT * FROM t_user WHERE ...
- 当前读:当前读就是读的是最新数据,而不是历史的数据,加锁的 SELECT,或者对数据进行增删改都会进行当前读。
SELECT * FROM player LOCK IN SHARE MODE;
SELECT FROM player FOR UPDATE;
INSERT INTO player values ...
DELETE FROM player WHERE ...
UPDATE player SET ...
2、首先确认一点MVCC属于快照读的,在进行快照读的情况下是不会对数据进行加锁。
- 当前读的情况下,mysql可重复读隔离级别,通过间隙锁+ 临键锁来解决幻读问题。
- 快照读的情况下,mysql可重复读隔离级别,通过MVCC解决的幻读问题。
MVCC原理
1、MVCC原理是什么?
InnoDB 实现MVCC,是通过Read View + Undo Log
实现的,Read View可见性规则帮助判断当前版本的数据是否可见,Undo Log 保存了历史快照。如果要查看的事务对应的数据,在Read View可见效规则判断不可见,则通过Undo Log保存的历史快照进行查询。
2、为什么可重复度隔离级别下,没有“不可重复度”的问题?
在可重复读(RR)隔离级别下,同一个事务里面,只会在开始时获取一次read view,后面都是共用同一个read view,从而保证每次查询的数据都是一样的。即:没有不可重复读问题。
在读已提交(RC)隔离级别下,同一个事务里面,每一次查询都会产生一个新的Read View副本,这样就可能造成同一个事务里前后读取数据可能不一致的问题(即:不可重复读并发问题)。
3、ReadView介绍
readview是事务执行SQL语句时,产生的读视图。实际上在innodb中,每个SQL语句执行前都会得到一个Read View。主要是用来做可见性判断的,即判断当前事务可见哪个版本的数据。
如何进行可见效判断呢?
- m_ids:当前系统中那些活跃(未提交)的读写事务ID, 它数据结构为一个List。
- min_limit_id:表示在生成Read View时,当前系统中活跃的读写事务中最小的事务id,即m_ids中的最小值。
- max_limit_id:表示生成Read View时,系统中应该分配给下一个事务的id值。
- creator_trx_id: 创建当前Read View的事务ID
-
如果数据事务ID
trx_id < min_limit_id
,表明生成该版本的事务在生成Read View前,已经提交(因为事务ID是递增的),所以该版本可以被当前事务访问。 -
如果
trx_id>= max_limit_id
,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。 -
如果
min_limit_id =<trx_id< max_limit_id
,需腰分3种情况讨论
- (1).如果m_ids包含trx_id,则代表Read View生成时刻,这个事务还未提交,但是如果数据的trx_id等于creator_trx_id的话,表明数据是自己生成的,因此是可见的。
- (2)如果m_ids包含trx_id,并且trx_id不等于creator_trx_id,则Read View生成时,事务未提交,并且不是自己生产的,所以当前事务也是看不见的;
- (3).如果m_ids不包含trx_id,则说明你这个事务在Read View生成之前就已经提交了,修改的结果,当前事务是能看见的。
4、Undo Log
作用:主要用于记录数据被修改之前的日志,在表信息修改之前先会把数据拷贝到undo log 里,当事务进行回滚时可以通过undo log 里的日志进行数据还原。
5、举例
a、原始数据
b、事务执行过程,MVCC原理
6、MVCC解决了幻读问题吗?
结论:MVCC本来就是对mysql不加锁的优化,快照读的情况下可以避免幻读问题,在当前读的情况下则需要使用间隙锁来解决幻读问题的。
举例:
1、快照读
在使用快照读的时候,数据是可以插入成功的,那这也就说明了一个问题,快照读的时候就根本没加锁,否则的话数据是不可能插入成功的,而且在插入数据提交成功后,我们执行第二条查询 语句是读取不到中间插入的这条数据的,这也就说明在没有加锁的情况下,基于历史版本的MVCC快照读是可以避免幻读问题的。
2、当前读
当前读的话就不同了,当前读每次都会读取最新的数据。所以两次读取中间如果可以插入数据(实际上另一个事务会因为前一个事物有锁的存在而阻塞住),那么就肯定会造成幻读问题,所以在当前读的情况下就必须通过一种方式来解决幻读问题,而这种方式就是采用加锁来解决。
例如:首先关闭数据库的自动提交事务功能, 使用当前读的方式演示和上面一样的流程,结果发现在当前读的时候没有提交事务之前是根本无法进行数据插入的,所以这也就说明了,使用当前读的时候会对这个范围内的数据进行加锁,所以无法在查询的范围内进行数据插入,这无疑也证明了在当前读的情况下mysql是使用锁的机制来避免出现重复读和幻读问题的。
比如:以下这种情况,就会有幻读问题。
因为事务A开始是快照读,没加锁,所以事物B没阻塞提交了。事务A执行更新时是当前读,自然可以进行数据读取。
详情看下面的文章
参考文章: