大家好,我是程序员牛肉。
这篇中我不讲MVCC的死概念。那玩意网上太多了,一抓一大把。我再复制粘贴一遍没有任何意义。
如果你还不知道什么是数据库的MVCC机制,我推荐你可以看一看黑马的MySQL课程里面的MVCC机制解析,又或者是小林coding的讲解。他们对于MVCC机制是什么已经介绍的很详细了。
而我们今天对MVCC的深入理解,将通过两个问题来表达:
我们来看看MVCC机制下的比较规则:
这个比较规则乍一看挺唬人的,但其实就一句话:“在MVCC机制下,一个事务要么读取自己修改过的数据,要么读取其他事务已经提交的数据。”
一定要理解我说的这句话,这将对你后面理解MVCC起到至关重要的作用。
让我们回到我开头的那个问题:MVCC是如何防止脏读和不可重复读的?
在RC的隔离级别下:
在一个事务中,每一次SELECT 都会生成一次快照,经由MVCC来确定能够读取哪个数据版本。
脏读的定义是:读取到其他事务未提交的数据。
而MVCC机制就是确保一个事务要么读取自己修改过的历史数据,要么读取其他事务已经提交的数据,这可不就防住了脏读嘛。
在RR的隔离级别下:
在一个事务中,只有第一次SELECT 会生成快照,此后一致沿用这个快照。
不可重复度的定义是:连续两次读取数据的结果不一致。
而MVCC是根据快照来判断当前事务能够读取哪一条数据历史记录的。如果我们一直沿用一个快照,那么可读取的数据版本就是同一个。同一个数据版本读数据得到的结果当然是一样的。这可不就防住了不可重复读嘛。
介绍完这两个,其实我们还可以做一下引申:为什么RC隔离级别下防不住不可重复读?
相信聪明的你很快就能知道答案:RC隔离级别下,每一次SELECT 都会生成一个新的快照。那么读取到的数据版本就可能不一致。自然有可能会出现幻读了。
第二个问题:MVCC机制一定能防住幻读吗?那他防不住哪些幻读?
看到我的问题,你就应该知道MVCC防不住所有的幻读。那MVCC能够防住哪些可能会发生幻读的场景呢?
下列的场景,当我们的事务B尝试往查寻结果集中尝试插入数据的时候,也不会发生幻读。
原因很简单:在事务B中虽然对结果集进行了插入,但我们的事务A在事务B还没有提交之前就进行了第二次查询。
也就是说:事务A是看不见这条事务B未提交数据的。而也符合MVCC的运行机制:在MVCC机制下,一个事务要么读取自己修改过的数据,要么读取其他事务已经提交的数据。
这其实就是B站很多UP主说的:“MVCC能解决当前读下的幻读”。但说实话按照我这种理解方式要好理解很多。
那MVCC防不住哪些幻读场景呢?
我们轻松一推理就知道了:在MVCC机制下,一个事务要么读取自己修改过的数据,要么读取其他事务已经提交的数据。
那我们有没有办法让这条插入记录变成事务A的历史修改数据呢?
当然有了,只要在事务B插入这条数据之后,我们update一下这条数据,就会把这条数据变成事务A的历史修改数据。
那么基于MVCC机制,事务A就可以读取到这条数据。
在这种情况下,MVCC是防不住幻读的,因为事务A的update把事务B的insert语句变成了自己的历史修改数据。
而MVCC让一个事务要么读取自己修改过的数据,要么读取其他事务已经提交的数据。
而这也就是很多UP主的“MVCC防不住一个事务中既有当前读,又有快照读的情况。”
在这里还需要注意了,很多人可能会认为事务A中的upate读不到事务B的插入数据,因为事务B的那条数据还未提交。
如果你有这个想法,那你还需要好好了解一下MySQL中的“快照读”和“当前读”。insert和update都是当前读,直接对最新的表数据进行修改,是没有快照这一说的。
那我们要如何防住这种幻读情况呢?
其实很简单,手动加锁就行了。加一个next_key 锁,锁住待查寻的数据范围就好了。而这也是MySQL的解决方案:
本图片来自小林coding
介绍到这里,相信你已经了解了“MVCC的运行机制”。我在介绍的时候已经尽量尝试少用专业名词和概念了。希望我的文章可以帮助到你。
对于MVCC,你的理解是什么呢?欢迎在评论区留言