事务隔离级别
事务并发执行时遇到的一致性问题
脏写
如果一个事务修改了另一个为提交事务修改过的事务,就意味着发生了脏写现象。我们可以把脏写现象简称为P0.假设现在事务T1和T2并发执行,它们都要访问数据项x(可以把数据项x当作一条记录的某个字段)。那么P0对应的操作执行序列如下所示:
p0: w1[x]...w2[x]...((c1 or a1) and (c2 or a2) in any order)
其中w1[x]表示事务T1修改了数据项x的值,w2[x]表示T2修改了数据项x的值,c1表示事务T1的提交(Commit),a1表示事务T1的中止(Abort),c2表示事务T2的提交,a2表示事务的中止,...表示其他的一些操作。从P0到操作执行序列中可以看出,事务T2修改了未提交事务T1修改过的数据,所以发生了脏写现象。
脏写现象可能引发一致性问题。比方说事务T1和T2要修改x和y这两个数据项(修改不同的数据项就相当于修改不同记录的字段),我们的一致性需求就是让x的值和y的值始终相同。现在并发执行事务T1和T2,它们的操作执行序列如下所示:
w1[x=1]w2[x=2]w2[y=2]c2w1[y=1]c1
很显然事务T2修改了尚未提交的事务T1的数据项x,此时发生了脏写现象。如果我们允许脏写现象的发生,那么在T1和T2全部提交之后,x的值是2,而y的值却是1,不符合 "x的值和y的值始终相同" 的一致性需求。
另外脏写现象也可能破坏原子性和持久性。比如说有x和y这两个数据项,它们初始的值都是0,两个并发执行的事务T1和T2有下面的操作执行序列:
w1[x=2]w2[x=3]w2[y=3]c2a1
也就是T1先修改了数据项x,然后T2修改了数据项x和数据项y,然后T2提交,最后T1中止。现在的问题是T1中止时,需要将它对数据库所做的修改回滚到该事务开启时的样子,也就是将数据项x的值修改为0。但是此时T2已经修改过数据项x并且提交了,如果要将T1回滚的话,相当于要对T2对数据库所做的修改进行部分回滚(部分回滚是指T2只回滚对x做的修改,而不回滚对y做的修改),这就影响到了事务的原子性。如果要将T2对数据库所做的修改全部回滚的话,那么明明T2已经提交了,它对数据库所做的修改应该具有持久性,怎么能让一个未提交的事务将T2的持久性破坏呢?所以这时候就会很尴尬。
脏读
如果一个事务读到了另一个未提交修改过的数据,就意味着发生了脏读现象,我们可以把脏读现象简称为P1。假设现在事务T1和T2并发执行,它们都要访问数据项x。那么P1对应的操作执行序列如下所示:
P1: w1[x]...r2[x]...((c1 or a1) and (c2 or a2) in any order)
脏读现象也可能引发一致性问题。比如说事务T1和T2中要访问x和y这两个数据项,我们的一致性需求就是让x的值和y的值始终相同,x和y的初始值都是0.现在并发执行事务T1和T2,它们的操作执行序列如下所示:
w1[x=1]r2[x=1]r2[y=0]c2w1[y=1]c1
很显然T2是一个只读事务,依次读取x和y的值。可以由于T2读取的数据项x是未提交事务T1修改过的值,所以导致最后读取x的值为1,y的值为0。虽然最终数据库状态还是一致的(最终变为了x=1,y=1),但是T2却得到了一个不一致的状态。数据库的不一致状态是不应该暴露给用户的。
P1代表的事务的操作执行序列其实是一种脏读的广义解释,针对脏读还有一种严格节食。为了与广义解释进行区分,我们把脏读的严格解释称为A1,A1对应的操作执行序列如下所示:
A1: w1[x]...r2[x]...(a1 and c2 in any order)
也就是T1先修改了数据项x的值,然后T2又读取了未提交事务T1针对数据项x修改后的值,之后T1中止而T2提交。这就意味着T2读取到了一个根本不存在的值,这也是脏读的严格解释。很显然脏读的广义解释是覆盖严格解释包含的范围内的。
不可重复度
如果一个事务修改了另一个未提交事务读取的数据,就意味着发生了不可重复度现象,或者叫模糊读现象。我们可以把不可重复度现象简称为P2。假设现在事务T1和T2并发执行,它们都要访问数据项x。那么P2对应的操作执行序列如下所示:
r1[x=0]w2[x=1]w2[y=1]c2r1[y=1]c1
很显然T1是一个只读事务,依次读取x和y的值。可是由于T1在读取数据项x后,T2接着修改了数据项x和y的值,并且提交,之后T1再读取数据项y。这个过程中虽未发生脏写和脏读(因为T1读取y的值时,T2已经提交),但最终T1的到的x的值为0,y的值为1。很显然这是一个不一致的状态,这种不一致的状态是不应该暴露给用户的。
P2代表的事务的操作执行序列其实是一种广义解释,针对不可重复度还有一种严格解释。为了与广义解释进行区分,我们把不可重复读的严格解释称为A2,A2对应的操作执行序列如下所示:
A2: r1[x]...w2[x]...c2...r1[x]...c1
也就是T1先读取了数据项x的值,然后T2又修改了未提交事务T1读区等数据项x的值,之后T2提交,然后T1再次读区数据项x等值时会得到与第一次读取时不同的值。这也是不可重复读的严格解释。很显然不可重复度的广义解释是覆盖严格解释包含的范围的。
幻读
如果一个事务先根据某些搜索条件查询出一些记录,在该事务未提交时,另一个事务写入了一些符合那些搜索条件的记录(这里的写入可以指INSERT、DELETE、UPDATE操作),就意味着发生了幻读现象。我们可以把幻读现象简称为P3。假设现在事务T1和T2并发执行,那么P3对应的操作执行序列如下所示:
P3: r1[p]...w2[y in P]...((c1 or a1) and (c2 or a2) any order)
其中r1[p]表示T1读取一些符合搜索条件P的记录,w2[y in p]表示T2写入一些符合搜索条件p的记录。
幻读现象也可能引发一致性问题。比如说现在符合搜索条件的记录条数有3条。我们有一个数据项z专门表示符合搜索条件P的记录条数,它的初始值是3。我们的一致性需求就是让z表示符合搜索条件P的记录数。现在并发执行事务T1和T2,它们的操作执行序列如下所示:
r1[p]w2[insert y to p]r2[z=3]w2[z=4]c2r1[z=4]c1
T1先读取符合搜索条件p的记录,然后T2插入了一条符合搜索条件p的记录,并且更新数据项z的值为4。然后T2提交,之后T1再读取数据项z。z的值变为了4,这与T1之前实际读取出的符合搜索条件p的记录条数不合,不符合一致性需求。
P3代表的事务执行操作序列其实是一种广义解释,针对幻读还有一种严格解释。为了与广义解释进行区分,我们把幻读的严格解释称为A3,A3对应的操作序列如下所示:
A3: r1[p]...w2[y in p]...c2...r1[p]...c1
也就是T1先读取符合搜索条件p的记录,然后T2写入了符合搜索条件p的记录。之后T1再读取符合搜索条件p的记录时,会发现两次读取的记录是不一样的。
SQL标准中的4种隔离级别
以上介绍了在并发事务执行过程中可能会遇到的一些现象,这些现象可能会对事务的一致性产生不同程度的影响。我们按照可能导致一致性问题的严重性给这些对象排一下序:
脏写>脏读>不可重复度>幻读
在这里,"舍弃一部分隔离性来换取一部分性能" 体现为:设立一些隔离级别,隔离级别越低,就越有可能发生越严重的问题。有一帮人(并不是设计MySQL的大叔)指定了一个SQL标准,在标准中设立了4个隔离级别。
- READ UNCOMMITTED:未提交读
- READ COMMITTED:已提交读
- REPEATABLE READ:可重复读
- SERIALIZABL:可串行化
SQL标准中规定(是SQL标准规定,不是MySQL中规定):针对不同的隔离级别,并发事务执行过程中可以发生不同的现象,具体情况如下:
隔离级别 | 脏读 | 不可重复度 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 可能 | 可能 | 可能 |
READ COMMITTED | 不可能 | 可能 | 可能 |
REPEATABLE READ | 不可能 | 不可能 | 可能 |
SERIALIZABL | 不可能 | 不可能 | 不可能 |
也就是说
- 在READ UNCOMMITTED隔离级别下,可能发生脏读、不和重复读和幻读现象;
- 在READ COMMITTED隔离级别下,可能发生不可重复读和幻读现象,但是不可能发生脏读现象;
- 在REPEATABLE READ隔离级别下,可能发生幻读现象,但是不可能发生脏读和不可重复读现象;
- 在SERIALIZABL隔离级别下,上述各种现象都不可能发生。
脏写是怎么回事?怎么上面没有提到?这是因为脏写这个现象对一致性影响太严重了,无论是哪种隔离级别,都不允许脏写的情况发生。
MySQL中支持的4种隔离级别
不同的数据库厂商对SQL标准中规定的4种隔离级别的支持不太一样。比如,Oracle就只支持READ COMMITTED和SERIALIZABLE隔离级别。MySQL虽然支持4种隔离级别,但与SQL标准中规定的各级隔离级别允许发生的现象有些出入————MySQL在REPEATBLE READ隔离级别下,可以很大程度上禁止幻读现象的发生。
MySQL的默认隔离级别为REPEATABLE READ。
设置事务的隔离级别
如果我们想让事务在不同的隔离级别中运行,可以通过下面的语句修改事务的隔离级别:
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL level;
其中,level有4个可选值:
level: {
READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABL
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。