吐槽:最近在找工作,今天着重看了一下mysql相关的东西,MVCC实现过程、以及如何解决幻读问题
一、事务隔离级别
1、读未提交(RU) --会产生脏读
2、读已提交(RC) --会导致不可重复读
3、可重复读(RR) --会产生幻读,mysql通过加锁解决了幻读
4、串行化(S) --事务顺序执行,不会出现上面的这些问题,但是效率慢
话不多说,下面直接记录下MVCC的实现细节
二、MVCC(多版本并发控制)
mysql数据库mvcc只作用在RC、RR两种隔离级别下,RU和S不需要并发版本控制,mysql默认事务隔离级别是RR。
重点:实现mvcc需要两个隐藏字段,我们创建的表中都有两个隐藏字段DATA_TRX_ID(操作该数据的事务ID)和DATA_ROLL_PTR(指向undo.log的指针);undo.log是一个链表(个人理解:每个行数据都有一个自己的undo.log链表,会将当前行数据历史版本的数据一链表的形式存储起来,用于快照读和回滚操作);
mvcc中还有一个重要的东西就是Read View,里面里几个参数需要只要一下:
mIds:当前活跃的事务id集合;
min_trx_id:当前活跃的事务id集合中最小的事务id(开启每个事务所分配的事务id都是按顺序递增的,id大的一定是在id小的之后开启的);
max_trx_id:最大事务id(这里的最大事务id不是mids集合中最大的事务id,而是数据库中已经申请到最大事务id);
creator_trx_id:创建Read View的事务id(当前事务id);
事务中读取数据时会用到Read View中的参数去删选每行数据的哪个版本是可以查出来的
RC(读已提交)隔离级别下:
每次查询数据时,都需要创建一个Read View,所以如果两个查询之间有其他事务修改了某个数据并提交了,两次查询看到的数据就不一样;
例如:事务A(事务ID为10)第一次查询创建的Read View属性为mIds = [10, 12, 16], min_trx_id = 10, max_trx_id = 20; 它会查询出 行数据 上事务id比10小的版本数据或者10 - 20 中间不在mIds中已经提交的版本数据;
如果第一次查询之后事务B(事务ID为21)修改了满足你查询条件的数据,并提交了事务;
事务A再查询相同条件的数据时,它会新创建一个Read View属性为mIds = [10, 12, 16], min_trx_id = 10, max_trx_id = 21; 你查询出的数据就是事务B(事务ID为21)修改后的数据了,应为他已经提交了;
再看RR级别下
RR(可重复读)隔离级别下:
事务A(事务ID为10)第一次查询创建的Read View属性为mIds = [10, 12, 16], min_trx_id = 10, max_trx_id = 20; 它会查询出 行数据 上事务id比10小的版本数据或者10 - 20 中间并且不在mIds中已经提交的版本数据;
如果第一次查询之后事务B(事务ID为21)修改了满足你查询条件的数据,并提交了事务;
以上和RC级别下情况一样;
此时事务A再去查询相同条件的数据时,不会新建Read View,使用的还是第一次查询创建的Read View 还是mIds = [10, 12, 16], min_trx_id = 10, max_trx_id = 20; 注意看max_trx_id还是20,他就取不到事务B(事务ID为21)修改的数据;因为在他看来最大的事务Id就是20,大于20的都是未来数据。
以上是RR级别下怎么解决不可重复读的问题(使用快照读,一个事务多次读取只在第一次读的时候创建Read View);
下面说下RR级别下怎么解决幻读的问题:
RR级别下解决幻读问题需要加锁(next-key lock 临键锁),临键锁是行锁 + 间隙锁共同使用的结果;
在一个事务中如果不想查询的数据被其他事务修改,需要使用当前读语法select for update来给数据加上左开右闭的临键锁(注意:如果查询字段是唯一索引的话,临键锁为退化为行级锁),其他事务操作临键锁内的数据会被阻塞;
即锁住当前值及当前值左右的间隙(针对索引值范围的边界条件),防止其他事务插入;
有问题请大佬们指出,勿喷!!!感谢。