脏读、不可重复读、幻读,你晕过么?

本文是论文《A Critique of ANSI SQL Isolation Levels》的一篇不正经翻译文章,而且只翻译了一丢丢。原文虽好,但实在晦涩。本文重新梳理了一下ANSI SQL标准中对脏读、不可重复读、幻读等现象的模糊定义,并提出了更多可能引发一致性问题的现象,以及定义了更多的隔离级别。

摘要

ANSI SQL92标准依赖这三个现象来定义隔离级别:脏读、不可重复读、幻读。本文就是给大家看看ANSI SQL标准中定义的这些现象不能说明一些流行的隔离级别,包括使用锁来实现的隔离级别。但是对这些SQL92标准对这些现象的定义比较模糊,我们觉得有必要仔细分析一下,才能得到更清晰的定义。另外,我们还引入了更多的现象来解释隔离级别,其中一个重要的隔离级别是:Snapshot Isolation。

1、简介

应用程序设计者可以允许并发的事务运行在不同的隔离级别上,从而牺牲一些正确性来换取更大的吞吐量。低的隔离级别提高了事务并发程度,但是增加了数据库中含有不正确数据的风险。令人惊喜的是,有一些事务运行在最高的隔离级别,但是有另外一些事务却运行在较低的、可以访问未提交状态的隔离级别。当然,运行在较低隔离级别的事务可能产生无效的数据。应用程序设计者必须阻止之后运行在较高隔离级别的事务去访问这些无效的数据,继而传播错误。

ANSI/ISO SQL92规范定义了4个隔离级别:

  • 读未提交(READ UNCOMMITTED)

  • 读已提交(READ COMMITTED)

  • 可重复读(REPEATABLE READ)

  • 可串行化(SERIALIZABLE)

这些隔离级别是依据经典的串行化定义,加上三种禁止的操作序列(英文名:,也被称作现象,具体指:脏读、不可重复读和幻读。也被称作现象,具体指:脏读、不可重复读和幻读)。这些现象的具体含义在ANSI SQL标准中并没有清晰的阐释,不过却隐含着这些现象对应的操作序列可能导致异常行为( 小孩子曰:这里省略了很多没用的话 )。

2、隔离定义

2.1、可串行性概念

一个事务包括一系列操作,这些操作把数据库从一个一致性状态转换为另一个一执行状态(可以把一致性理解为符合现实世界的游戏规则)。单词:“history”指的是一个抽象的过程,它包含若干个针对特定数据的读写操作,这些操作分属于不同的事务。在同一个history中,如果分属于不同事务的两个操作访问了同一个数据项,并且其中的一个操作是写操作的话,那么就会发生冲突。这里的数据项可以是记录,也可以是页面,也可以是整个表或者别的什么东西。冲突的操作也可能发生在被锁定的一组数据项以及单个数据项上( 小孩子曰:说的是真的绕,有必要这么绕么… 其实这里说的history就是指的一组分属于不同事务、针对特定数据项、按照一定顺序进行的一些读写操作 )。

这里是介绍如何根据history判断某些事务的并发执行是否可以等同于它们的串行化执行,个人感觉跟后边的事情不太搭,就不翻译了

2.2 ANSI SQL隔离级别

隔离级别不仅仅可以使用锁来实现,设计ANSI SQL的隔离级别的同学考虑到了这一点,他们用下边这三种现象来区分不同的隔离级别:

  • P1(脏读):事务T1修改了数据项。然后另一个事务T2在T1执行COMMIT或ROLLBACK之前读取该数据项。如果T1之后执行了ROLLBACK,则T2读取了从未提交,也就是从未真正存在过的数据项。

  • P2(不可重复或模糊读):事务T1读取数据项。然后另一个事务T2修改或删除该数据项并提交。如果T1之后尝试重新读取该数据项,它将看到一个修改后的值或发现该数据项已被删除,这与它上一次读取的效果不一样。

  • P3(幻读):事务T1读取满足某些的一组数据项。然后,事务T2创建了满足T1的的数据项并提交。如果T1之后以相同的重复读取,则它将看到与第一次读取不同的一组数据项。

在一个可串行化的history( 再次声明一下,这个history指的就是不同事务针对同一数据项、按照一定顺序进行的一系列读写操作 )中是不会发生上述三种现象的。

为了能更简单、直观的表示交叉执行的事务所做的操作,我们这里为这些操作搞一些简化形式,比方说:

  • “w1 [x]”表示按事务1在数据项x上进行写操作

  • “r2 [x]”表示事务2对x的进行读操作

  • “r1 [P]”表示事务1对满足搜索条件P的一组记录进行操作

  • “w1 [P]”表示事务1对满足搜索条件P的一组记录进行写操作

  • “c1”表示事务1的提交

  • “a1”表示事务1的中止( 中止的英文翻译为abort,简写为a ))

现在就用这些操作的简化表示来描述一下之前提到的三种现象。P1(脏读)可以按照下边的方式进行描述:

(2.1) w1[x]...r2[x]...(a1 和 c2 可以按照任何顺序发生)

但是ANSI SQL标准对于P1(脏读)的描述是模糊的,它实际并没有确定的说发生P1(脏读)时T1是否必须中止,它只是说如果T1中止会发生一些不幸的事情,有一些同学可能就把P1(脏读)理解成了下边这样:

(2.2) w1[x]...r2[x]...((c1 or a1) and (c2 or a2) in any order)

也就是说在执行了 w1[x] r2[x] 操作后,T1可以提交或者中止,T2也可以提交或者中止,并且我们也不关心是T1提交或中止,还是T2先提交或中止。简而言之就是在执行了 w1[x] r2[x] 操作后,我们不关心之后T1和T2发生了什么。

很显然,(2.2)中描述的P1(脏读)比(2.1)中的范围更广,因为最后T1和T2的提交和中止一共可能有四种( 小孩子曰:分别是:c1,c2、c1,a2、a2,c1、a2,a1 ),(2.2)中这四种情况只要有一种发生,就意味着发生了P1(脏读),(2.1)中只有2种情况( 也就是:a1,c2、a2,c1 )下才算发生了P1(脏读)。

我们把(2.2)称作P1(脏读)的广义解释,把(2.1)称作P1(脏读)的严格解释。(2.2)代表的广义解释阐述了一种将来可能发生异常的可能性,(2.1)代表的严格解释表示了具体的异常实际指的是什么。为了区别这两种解释,我们把P1(脏读)的广义解释还称作P1,把P1(脏读)的严格解释称作A1。也就是下边这样:

P1: w1[x]...r2[x]...((c1 or a1) and (c2 or a2) in any order)

A1: w1[x]...r2[x]...(a1 and c2 in any order)

小贴士:不知道大家看懂这段没,小孩子再来自己解释一遍。对于脏读的广义解释来说,其实就是指我们实际情况中只要发生了w1[x]…r2[x],我们就认为发生了脏读。对于脏读的严格解释来说,其实就是指在实际情况中发生了w1[x]…r2[x]之后,T1必须中止,T2必须提交后才认为是发生了脏读。而广义解释只是说明了一种造成数据异常的可能性,狭义解释指的就是真正的数据异常指的是什么。

同样P2(不可重复或模糊读)和P3(幻读)也拥有广义和严格的解释,我们把广义解释分别称为P2和P3,把严格解释分别称为A2和A3:

P2: r1[x]...w2[x]...((c1 or a1) and (c2 or a2) in any order)

P2: r1[x]...w2[x]...((c1 or a1) and (c2 or a2) in any order)

A2: r1[x]...w2[x]...c2...r1[x]...c1

P3: r1[P]...w2[y in P]...((c1 or a1) and (c2 or a2) any order)

A3: r1[P]...w2[y in P]...c2...r1[P]...c1

需要注意一下,在ANSI SQL标准中,对于P3(幻读)来说,仅仅是声明了T1先读取了符合某些搜索条件的记录,之后T2插入了符合同样搜索条件的新记录,落脚点是插入操作;而本文中将P3描述成了T1先读取了符合某些搜索条件的记录,之后T2对满足同样搜索条件的记录做了写操作(这里的写操作包括插入、更新和删除)。

ANSI SQL标准定义了如下表所示的4个隔离级别,每个隔离级别的特征在于禁止事务运行过程中可能发生的现象。但是ANSI SQL标准对于SERIALIZABLE隔离级别的定义却不依赖于这些现象,他们称SERIALIZABLE隔离级别必须提供“commonly known as fully serializable execution”的功能。所以下表其实带来了一个常见的误解,就是很多同学认为禁止了这3种现象的隔离级别就是SERIALIZABLE。本文的第3节表明,即使禁止了这些现象也不能保证真正的可串行性。

小贴士:什么是个commonly known as fully serializable execution,这个好像要解释很久:joy:,《数据库系统概念》这本书里有比较细的解释,同志们可以翻翻看。

这里戛然而止…

小孩子曰:翻译到这里手麻了,后边还有非常长的内容,大家自己阅读原文看看吧。或者想获取论文的百度云链接,请在公众号后台输入“隔离级别”获取。

小青蛙历史文章

小册补充文章集合

彻底解决MySQL中的乱码问题

MySQL中包含IN子句的语句是怎样执行的

InnoDB的自增键和row_id用完了会发生什么?

收藏版MySQL语句加锁分析

死锁分析

听说有一个最左原则?这回终于讲清楚了MySQL执行查询时联合索引用几个列的问题

MySQL中IS NULL、IS NOT NULL、!=不能用索引?胡扯!

长按关注小青蛙,都是干货喔