从前,有一对夫妻,他们琴瑟和鸣,举案齐眉,靠着不断地购买,终于积累了一笔可观的家产。
1000根烤羊腿!
这1000根烤羊腿是丈夫和妻子分别持有的,丈夫持有800根,妻子持有200根。谁知有一天,晴空劈下来一道大雷,国家突然要以家庭为单位征收烤羊腿税了!
以家庭为单位持有烤羊腿超过500根,就要交税啦。这可咋办啊,丈夫和妻子商量出了一个主意,俩人假离婚,丈夫再过户300根烤羊腿给妻子,这样他俩正好在500根红线上,不用交税!
说干就干,俩人决定利用一个MySQL数据库完成300根烤羊腿的过户,他们觉得数据库能看得见,不会作弊,是最公平的。
殊不知,他们并不了解数据库底层存储引擎的事务和锁机制,他们的命运将被底层代码安排的明明白白。
下面,我们就从丈夫、妻子和银行三方的视角来看看这场闹剧是怎么收场的。
首先,丈夫和妻子在银行开了户,这个户头里的一个数据表存储了他们俩分别的烤羊腿数量。他们俩和银行都查了一下,一切正常!(最左边是丈夫,中间是妻子,右边是银行)
现在,我们来看看三种情况。
场景一 丈夫和妻子都是小白兔,彼此信任
丈夫和妻子按照事先商量好的方案,同时开启了事务,目标是丈夫转出300个羊腿,妻子得到300个羊腿,俩人最终的羊腿都变成500根。
从他俩写的代码可以看出,丈夫和妻子分别把名下的羊腿数目更新成500了,更新完后,他俩顺手查了下现在的羊腿数。
有趣的是,丈夫和妻子此时只能看到自己所做的更新,而看不到对方的。这是由于他俩处在两个独立的事务中,没有提交事务前,彼此是看不到对方事务里所做的修改的。
不过他俩互相信任,相信对方都做出了正确的更新。
这时候的银行呢?它看到的是什么呢?
银行实惨,它游离于丈夫和妻子的两个独立的事务外,看不到任何一个事务内部所做的更新。在它看来,这俩人的资产还和原来一样。
出于对彼此的信任,丈夫和妻子都敲入了代码 commit;,然后丈夫妻子和银行三方进行了一次查询。
银行蒙了啊,这俩人啥时候完成的羊腿转移啊?丈夫和妻子则顺利完成了他们的羊腿转移。
大胆的发散一下,其实我们通过银行给对方转账时,银行的数据库就走了一遍上述的底层逻辑。银行要的就是最后的结果没问题,至于转多少,怎么转,它不干预独立的事务。
让我们回到羊腿转移事件上,来看看第二个场景。
场景二 丈夫心怀毒计,妻子单纯善良
或许是想把这场假离婚坐实,或者是早就对妻子那200根烤羊腿觊觎已久,他决定使坏。
从头开始。
丈夫和妻子同时开启了自己独立的事务,但是这次丈夫不打算履行约定了。
从丈夫写的代码就能看出,他要完全霸占妻子的烤羊腿!而妻子显然相信丈夫,觉得他会把300个烤羊腿给自己,于是将自己的烤羊腿数量改成了500。
接下来丈夫开始了他的计划,他直接把妻子的烤羊腿数量改成了0!
但是,没想到的是,这次更改竟然被阻塞了!!
是丈夫被发现了吗?不是,他在无意中触发了innodb存储引擎的行级锁。
因为妻子先他一步对属于自己的一行数据进行了修改,这时候MySQL数据库会出于对数据的保护把这行锁定,让妻子的更改没有提交事务之前无法接受别的事务内的更改。
恶毒的丈夫和善良的妻子根本不知道底层发生的这一切,妻子最终还是提交了她的更新。如果她早一点提交的话,丈夫的毒计就得逞了!
因为行级锁会随着事务的提交而被另一个事务获得并解除,另一个事务内的更新就可以运行了。
但是现在,丈夫傻眼了。
超时了!!善良的妻子被超时机制保护了!(其实是我的善良,我故意多拖了一会才提交了妻子的事务)丈夫的毒计没有得逞,他对妻子烤羊腿的更新被回滚了。
无奈,他也只能提交了。现在我们再看看他俩和银行的查询。
妻子和银行都傻眼了,妻子蒙了,我的数量是对的,丈夫怎么还多了?不过聪明的她很快想明白了,既庆幸,又绝望。
银行怒了,你俩这是平地起了500个烤羊腿啊!作废!这笔交易作废!
MySQL的行级锁恰如其分的保护住了妻子的利益,我们也能明白两个独立事务之间的互相更新是很容易触发行级锁的。
接下来,我们再来看看更狗血的情况。
场景三 丈夫和妻子各怀鬼胎,狗咬狗,一嘴毛
这场突如其来的羊腿纳税风波成为了摧毁他们婚姻的最后一根稻草,丈夫和妻子各怀鬼胎,都打算霸占对方的羊腿。
好戏开始了。
俩人和没事人一样各自开启了事务,准备大干一场了。
因为各怀鬼胎,这俩人上来先不改自己的,先去改对方的烤羊腿数量!
看看这俩人写的代码,彼此都把对方的数量改成0,这种匪夷所思的操作真是闻所未闻。
改完之后,丈夫先动手了!他要把自己的烤羊腿数量改成1000,妻子当然也不逞多让,她也要把自己的烤羊腿数量改成1000!
这时候,有意思了!死锁了!
为什么会死锁?这俩人咎由自取,原来丈夫改了妻子的数据,在丈夫的事务里妻子的数据被加了个行级锁,同理妻子的事务里丈夫的数据也被加了个行级锁。他俩再分别改自己的数据的时候,锁都在对方手里,既提交不了,又拿不到锁,所以就死锁啦!
他俩根本不知道这背后的逻辑,竹篮打水一场空,互相算计,两败俱伤。
这便是MySQL的innodb引擎中行级锁的死锁情况,互相争夺资源,互相锁死。
通过这对夫妻的故事,我们也很好的理解了MySQL数据库中事务和锁的运行机制,这对我们开发高性能高并发的MySQL数据库是很有意义的