这是小小本周的第一篇。

今天干了啥

今天可是周日,一个休息日,对于休息日来说,小小本身也是比较忙碌的,忙碌的小小,耗费的很多的时间,终于倒腾完成了GitChat,一篇GitChat 将会于近日出炉。
完工页面
面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图

好啦,正式开工今日正文。今日更新面试题文。

脏读,幻读,不可重复读

既然说到幻读了,那么就先说数据库的三大问题,分别是幻读,脏读,不可重复读。

脏读

所谓的脏读是指一个事物中访问到了另外一个事物中未提交的数据。如下图所示。
面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图(1)
如果会话2更新age为10,但是在提交之前会话1希望得到age,那么此时获取到的值为更新前的值,或者如果会话2更新了值,但是执行了rollback,此时会话1拿到的值仍然为10,这为脏读。

幻读

一个事物读取2次,得到的记录条数不一致
面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图(2)
上图很明显的展示了这个情况,由于在会话1之间插入了一个新的值,所以得到的两次数据就不一样了。

不可重复读

一个事物读取同一条记录2次,得到的结果不一致。
面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图(3)
由于在读取中间变更了数据,所以会话 1 事务查询期间的得到的结果就不一样了。

隔离级别

还需要一点隔离级别的知识。
隔离级别分为四种隔离级别。

READ UNCOMMITTED

可以读取未提交的数据,未提交的数据称为脏数据,所以又称脏读。此时:幻读,不可重复读和脏读均允许;

READ COMMITTED

只能读取已经提交的数据;此时:允许幻读和不可重复读,但不允许脏读,所以RC隔离级别要求解决脏读;

REPEATABLE READ

同一个事务中多次执行同一个select,读取到的数据没有发生改变;此时:允许幻读,但不允许不可重复读和脏读,所以RR隔离级别要求解决不可重复读;

SERIALIZABLE

幻读,不可重复读和脏读都不允许,所以serializable要求解决幻读;

结论

这里先说结果
在REPEATABLE READ隔离级别下,innodb使用MVCC和next-key locks解决幻读,mvcc解决的是普通读,包括快照读引起的幻读,next-key locks解决的是当前读情况下的幻读。

如何解决

当前读

当前读,指的是加锁的select。和 update,delete语句,在RR的事物隔离级别下,数据库会使用next-key locks来锁住本条记录和索引区间。
拿上面的例子来说,在rr的情况下,假设当前使用的是当前读,加锁了读。
select * from table where id>3 锁住的就是id=3这条记录以及id>3这个区间范围,锁住索引记录之间的范围,避免范围间插入记录,以避免产生幻影行记录。

普通读

因为普通读,是不会加锁的读,故不会有next-key locks的使用,解决幻读的手段为MVCC
MVCC会给每行元祖加一些辅助字段,记录创建的版本号,和删除版本号。
每个事物在启动的时候,都会有一个唯一的版本号,每开启一个事物,事物的版本号递增。
在RR下,增删查改是这样的。

SELECT

读取创建版本小于或等于当前事物版本号的,并且删除版本为空或大于当前事物版本号的记录,这样可以保证在读取之前记录是存在的。

INSERT

把当前事物的版本号保存到行的创建版本号。

UPDATE

新插入一行,并以当前事物的版本号作为新行的创建版本号,同时把原记录行的删除版本号设置为当前事物的版本号。

DELETE

把当前事物的版本号保存到行删除的版本号。

举个例子

当我插入一条记录,事物id假设为1,记录如下,即,创建版本号就是事物的版本号。
面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图(4)
如果进行更新,事物id为2.
面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图(5)
如果把name更新为taotao,原来的元祖deleteversion版本号为这个事物id,并且新增加一条。

如果我删除的话,假设事物为id为3
事物变成了如下
面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图(6)

如果我读取的时候,必须要满足两个条件。
1. 读取创建版本号小于或者等于当前事物版本号,这意味着数据在这个事物之前被创建。
2. 删除版本为空或者大于当前事物版本号的记录,意味着删除操作在这个事物之后发生。

举个例子。
当前数据库的状态。
面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图(7)
如果事物A的id为10,现在update table set name = “hh” where id > 3 执行这条语句。
面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图(8)
事物B的id为11.

insert into table values(11, uu);

面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?插图(9)

最后事物A id=10 在此读取

select * from table where id>3

根据上述的规则,

读取创建版本好小于等于当前事务的→那么(4,a)(5,b)(4,hh)(5,hh)
删除版本为空或大于当前事务版本号的记录→(4,hh)(5,hh)
如此读取就没有读取到事务B新插入的那行,解决幻读

最后

我是小小,你若不在,我也一直在,你若在,我也一直在,我是小小,我们下期再见。