在了解mvcc之前,我们先来思考一个问题
示例1:
假设学生id=1的这个学生年龄为9,这个时候有2个事物进行操作
事物1:update t_student set age = 10 where id =1;
事物2:select age from t_student where id = 1;
事物1,将学生id=1的这个学生年龄修改为10但是事物没提交,这个时候事物2读取这个学生的年龄,那么是不是事物2读到事物1未提交的数据,这有个专有名词脏读
示例2:
事物1:select age from t_student where id = 1; age = 9
事物2:update t_student set age = 10 where id =1;提交
事物1:select age from t_student where id = 1; age = 10
同一个事物前后2次读到数据不一致了,这有个专有的名词不可重复读
示例3:
事物1:select count(*) from t_student where age >10 and age <20; 10条
事物2;insert into t_student (age) values (12);
事物1:select count(*) from t_student where age >10 and age <20; 11条
同一个事物前后2次读到结果集不一样,这有个专有的名词幻读
由此可见在单线程下没有问题,并发执行下多个事物同时执行就会有问题
那么如何解决解决上面的脏读、不可重复读、幻读问题呢?
为此数据库设计了4种隔离级别来应对上面的问题
四种隔离级别
mvcc机制
mvcc中文叫多版本并发控制,顾名思义是通过版本号控制的,那么这个版本信息是记录在哪里的呢?答案是记录在undo日志里,并发执行的时候多个事物的操作记录都记录在undo日志里,多个操作记录形成一个链条,每一个记录都会指向它的上一个记录,那么他是如何做到的呢?innod引擎默认给数据库表新增2个字段,一个是事物id(trx_id),递增,一个是回滚指针(roll_pointer),用来指向它的上一个操作记录,如图在数据库4种隔离级别下,随着隔离级别的升高,问题逐渐被解决,那么在串行化的隔离级别下,是通过加锁的方式,保证多个并发执行下的多个事物逐个执行,这个保证事物执行时候的准确性,但同时也牺牲了性能,那么有没有一种不加锁的机制呢?因为大部分情况下,都是读的场景,读数据场景如果也加锁那就太不友好了,正式基于这样的思想才有了mvcc机制,读不加锁,读写不冲突,提高并发能力
那么这个
刚才我们说了,读是不加锁的,他是从版本链中获取数据的,那么版本链中有好几条数据,那么应该读取哪一条数据呢?时间点不同怎么保证读取的数据是准确的的呢
那么就涉及到另外一个概念了,ReadView,快照读,mvcc将所有操作定义为2种读,即快照读和当前读
快照读:就是普通读,select age from t_student where id = 1;不加锁
当前读:就是写操作如:delete、insert、update、以及排它锁for update,和x锁,lock in share model。这些操作加锁
那么ReadView长什么样子呢?4个字段的数据结构
现在有了ReadView那么怎么提取版本链中的数据,具体规则如下:
如果被访问版本的 事务ID = creator_trx_id,那么表示当前事务访问的是自己修改过的记录,那么该版本对当前事务可见;
如果被访问版本的 事务ID < up_limit_id,那么表示生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的 事务ID > low_limit_id 值,那么表示生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的 事务ID在 up_limit_id和m_low_limit_id 之间,那就需要判断一下版本的事务ID是不是在 trx_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问; 如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。
MVCC 只在 Read Commited(读已提交) 和 Repeatable Read(可重读读)两种隔离级别下工作。 在RC隔离级别下,是每个快照读都会生成并获取最新的Read View,这就是我们在RC级别下不可重复读的原因。在RR隔离级别下,仅在第一次执行快照读的时候生成ReadeView,后续快照读复用同一个ReadView,从而做到可重复读。那么RR隔离级别为什么解决不了幻读:当2次快照读之前存在当前读,那么ReadView就会重新生成导致产生幻读