上篇推文中,我们了解到Mysql MVCC的相关知识:一文理解Mysql MVCC。今天我们就用学到的相关知识,解决一个问题。

我们都知道,Mysql Innodb引擎的默认事务隔离级别是RR可重复读,也就是在同一个事务中,多次读取相同的数据结果相同。而其底层就是通过:“排它锁+MVCC”来实现的。

话不多说,我们来看看下面的这个问题:




mysql 更新varchat mysql 更新丢失_sql


我们可以看到,上面的事务A在更新数据之前,数据已经被事务B所修改,但是事务A最终提交的时候,将事务B的提交覆盖掉了,导致了事务B的更新丢失。

用我们之前学过的MVCC来理解一下为什么这个问题会发生:

首先由于事务A开启的时候,事务B还没提交,此时A事务进行了一次select操作,导致生成ReadView。根据这张图的原理:


mysql 更新varchat mysql 更新丢失_mysql 更新varchat_02


B事务处于trx_ids的事务中,所以A事务无法看到B事务的数据处理过程,即B事务对数据的操作,对A事务不可见。这也正是可重复读的实现的原理。

而恰恰是因为A事务看不到B事务对数据的更新,而A事务本地无论如何都是读到该数据的1000元可重复读的版本,导致A事务后面的更新操作直接在1000元这个版本上对数据添加100,将B事务的更新完全覆盖。

那么如何解决上面的这个问题呢?

我们来看看上面问题的本质,其实就是在事务B操作数据之前,我们就调用了select,导致生成了ReadView,而我们又拿着这个select的数据去做后面的处理,最终导致了B数据的丢失。

看到这里可能有人会想,那等B事务的操作commit了之后再去做A事务的select操作不就不会有这个问题了吗?说的确实没错,按这么操作,B事务确实处在A事务可见区内,最终不会导致B事务的更新丢失。可是在并发情况下,你怎么知道什么时候会突然有个B事务来更新呢?

于是我们最好的做法就是在A事务的查询时,添加一个锁,mysql中的语句如下:


select * from demo where id = 1 for update;


这样在A操作事务前,B事务就无法获取到锁,也就无法进行更新操作了。从而避免了更新丢失。

其实上面的更新丢失情况,术语叫做:第二类更新丢失问题。而解决办法也有多种角度,如上面的加悲观锁方式,或者乐观锁方式(类似CAS)也可以处理此类问题。

不过强哥还是比较喜欢上面悲观锁的方式,方便且简单有效,其他的解决办法有兴趣的同学可以去网上找找我就不在这里说明了。

相信大家还有一个疑问,既然有第二类更新丢失问题,那么肯定也有第一类吧,没错,第一类更新丢失又叫回滚丢失:


mysql 更新varchat mysql 更新丢失_sql_03


不过,各种数据库的各种隔离级别都不允许此类问题的产生,所以就不在这里赘述了。至于数据库是如何避免这种问题的发生,强哥也找了好多资料,可是还是没有获取到有用的原理说明,大多类似如下的解释:


mysql 更新varchat mysql 更新丢失_数据_04