[译] 理解MySQL隔离级别:可重复读

之前写了一篇关于MySQL锁的机制的文章,其中提到了MySQL默认事务隔离级别,正好最近看到一篇文章感觉非常有趣,就决定翻译出来给大家分享一下,原文在此,译文如下(文中的所有语句与测试译者都已做过同样的测试,并得到相同的结果)。

隔离级别是MySQL中不常被提到主题,相关文档对于锁问题进行了简要的描述但却并没有探讨每个隔离级别的具体语义。这不光是MySQL文档的问题,也是SQL标准自身的一个模糊的地带。
MySQL相关文档与SQL标准对预期行为深度描述的缺失导致隔离级别成为了DBA和开发者心中臆断而非掌握的知识点。在这篇文章中,我旨在帮助大家理解MySQL默认隔离级别是如何工作的,并告诉大家一些惊人的事实。
在这之前,先让我们来看看SQL标准中是如何描述隔离级别的:“事务隔离级别定义了操作数据的程度,或是在并发事务场景下修改数据的执行计划”(这句话我也只能意会,真心翻译有障碍)。用人话讲,隔离级别定义了并发事务在修改数据的时候是如何相互影响的。
MySQL使用可重复读作为默认的事务隔离级别。以标准的描述,这个级别禁止了脏读(未提交数据不可见)和不可重复读(执行同一句查询应该返回同样的数值)并允许幻读(新加的数据行可见)。但是MySQL换了一种方式实现,让我们来看看它是怎么做的。

MySQL 可重复读测试

我们创建两个MySQL客户端连接,我们叫他们小蓝和小红(作者注:巴萨球服,纯属巧合。译者是个曼狗,哎)。在小蓝中我们创建了一个叫isolation的数据库和名为repeatable_read的表,这在我们后面的测试中会用到。


13670604-94c666f192f18216.png
创建数据库和表

没有幻读。。只有幻写!

我们发起一个事务,通过查询tx_isolation变量的值来查看当前的事务隔离级别,并检索repeatable_read表的内容,这样我们就创建了这个事务中关于这个表的快照。


13670604-3d89354fbc8bdf6f.png
开启事务创建快照

我们移步到小红然后添加两条记录。然后提交这个事务保证数据被真正写入。


13670604-6385ae67bb3ec59f.png
添加记录

接下来我们在小蓝中查询一下数据。
13670604-c2bb042a1e87bafb.png
查询

正如我们所见,MySQL里面的可重复读避免了幻读,因为新加的数据行没有被检索到。这比SQL标准的描述还要严格。但如果我们尝试更新的时候会发生什么?直觉上应该也是不会有任何更新。


13670604-ab26c4dcba2eae20.png
更新

Surprise!更新结果显示有一行匹配到了筛选条件并且已经被修改。让我们再来查询一次看看发生了什么。
13670604-255625c7151035c7.png
再查一次

这时候我们看到了一行之前更新出来的数据。这个情况完全出乎了我们的预料,因为这个表从来没有只有一行数据的情况,我们一来就添加了两条数据啊!我们看到了这个表从未存在的一个视图。当我们提交之后再来查询,如我们所想,我们看到了我们刚刚修改的那一行和之前在小红中添加好的另一行。
13670604-078214b420595c84.png
最终一致

更多的幻写

现在我们在小蓝开启一个事务并且获取内容来创建一个快照(或者我们也可以称之为版本)。


13670604-11ba8694b12bad2d.png
创建快照

回到小红,我们开启一个事务来更新记录。注意,幻读只会在新数据行上产生作用,而不会发生在已存在的数据行。


13670604-33c2467f19a8580a.png
更新数据行

让我们来看看在小蓝中会有什么。
13670604-5f38b63a2b88461d.png
获取并更新

最初我们看到数据表并没有什么变化。但是当我们基于会话中获取的数据来尝试更新的时候发现没有任何行满足条件。我们看到有一行的text字段值是modified,但是更新的时候却找不到了。当我们使用不会被其他事务更新的字段作为更新条件(比如id字段),我们才能完成更新。这次更新之后我们才能看到之前被小红更新的新的text字段值(first row 2)。

所见非所写

我们回到最初的数据值并创建一个额外的表来完成我们后面的测试。

13670604-46af029da031f025.png
还原创建新表

如之前所做的,我们开启一个新的事务并且获取全表数据来创建快照。
13670604-48e1df85e4592509.png
创建快照

现在我们回到小红来更新全表数据。
13670604-48ca5449b3cd8377.png
更新全表

然后我们回到小蓝,我们通过 insert into ... select 语句把repeatable_read表的数据“克隆”到repeatable_read_copy表中。然后我们检查两个表的各自的数据。
13670604-0077b0635a575e31.png
两个表的数据

我们塞进拷贝表里的数据与我们从原表中查到的数据是完全不同的。一旦我们提交了这个事务,如我们所预料的,我们才能够在原表中看到修改的数据。
13670604-d58038cf5d4bd0af.png
最终一致性

结论

经过这些测试,我们发现了关于MySQL的可重复读隔离级别如下现象:

  • 如果仅仅使用select语句,MySQL的实现比SQL标准显得更加严格,幻读并不会发生。因为快照是作用在所用表的所有行,这一点我们可以通过带--single-transaction参数的mysqldump命令来查看。
  • 如果事务修改了数据,则表现出来的行为混合了可重复读(没有修改的行是可见的)和读已提交(修改的行是可见的)。我们不能说这不符合标准因为这些现象并没有在标准中描述而且不属于我们常说的三种并发现象:脏读不可重复读幻读
  • 当事务基于已存在的数据来做修改的时候,它最终使用的是已提交的数据,而非快照数据。这对修改的和新数据行都成立,模仿了读可提交的行为。
    MySQL实现可重复读的方式并不服务我们的直觉,并且,即使它可以支持反复执行语句,它还是会在并发事务中修改数据和转移数据的时候产生问题。如果你的应用有可能会面对这些问题,你就需要修改你的查询使用select ... for update语句来增加数据库锁来保证你的同步性与可见性。

猜你喜欢

转载自blog.csdn.net/weixin_33704591/article/details/87030381
今日推荐