如果说数据不在ACID特性的保护下会发生不一致的现象,那么:
在ACID的保护下,是不是数据就一定不会产生不一致的现象呢?
在关系数据库系统中,多个会话(session)可以访问同一个数据库的同一个表的同一行数据。这样,对于数据而言,就意味着在同一个时间段内,有多个会话可以对其施加操作(或读操作或写操作),读写操作施加的顺序不同以及事务中A特性对事务结果的影响(或成功或失败,即要么提交要么回滚),这三种因素叠加在一起,会存在几种对数据有不同影响的情况:
q 读读操作:如果同时只存在多个读操作,对于数据自身则没有影响;既读和读操作互不相应数据的一致性,读读操作可以并发执行。
q 读写操作:如果读写操作都存在,因写在前读在后(如脏读现象)、读在前写在后(如不可重复读现象)、或者读在前写在后然后又读(如幻象现象),就可能因数据被写而导致另外一个读操作的会话读到错误的数据。
q 写写操作:如果同时存在多个写操作,写写操作直接改变了数据在同一时刻的语义,这就更不被允许,所以写写操作通常不允许被并发执行。但是,如果不做并发控制,写写并发操作也会带来数据异常现象。
这三种情况的第二种,对应了SQL标准中定义的三种数据异常的现象,注意这三种异常都是针对某个事务(第2.2.1节称这样的事务为“主事务”)的读操作而言的。SQL2003标准对于数据异常现象的定义如下:
The isolation level specifies the kind of phenomena that can occur during the execution of concurrent SQL-transactions. The following phenomena are possible:
1) P1 (“Dirty read”): SQL-transaction T1 modifies a row. SQL-transaction T2 then reads that row before T1 performs a COMMIT. If T1 then performs a ROLLBACK, T2 will have read a row that was never committed and that may thus be considered to have never existed.
2) P2 (“Non-repeatable read”): SQL-transaction T1 reads a row. SQL-transaction T2 then modifies or deletes that row and performs a COMMIT. If T1 then attempts to reread the row, it may receive the modified value or discover that the row has been deleted.
3) P3 (“Phantom”): SQL-transaction T1 reads the set of rows N that satisfy some <search condition>. SQL-transaction T2 then executes SQL-statements that generate on
<search condition>, it obtains a different collection of rows.
怎么理解上面的内容呢?从SQL标准的定义可以看出:
q 首先,涉及到了并发事务(concurrent SQL-tra nsactions):至少有两个事务同时发生,如上面所举的三种异常现象中,分别使用了事务T1和T2。但是,更为复杂的情况,如下一节所谈到的“写偏序(write skew)”现象的发生可以是两个或三个事务同时发生。
q 其次,涉及到了隔离级别(isolation level):如果多个事务是“可串行化”的,则意味着事务之间不应该互相影响(不是“一定互不影响”而是“不应该互相影响”),即逻辑上不应当存在“读写”冲突,所以数据库引擎实现时应该避免“读写”冲突造成的数据不一致的现象。避免的含义就是在数据库引擎编码实现的时候,采取并发控制技术,消除“脏读”、“不可重复读”、“幻象”这三种现象,采取的技术就是分级控制(不同的隔离级别),分别使用“读已提交(READ COMMITTED)”、“可重复读(REPEATABLE READ)”和“串行化(SERIALIZABLE)”这几种隔离级别,来应对这三种数据不一致的现象。
q 数据操作的对象,脏读和不可重复读是以“row”(一行)为单位,而幻象现象操作的是一个数据集(零行到多行)。
下面,我们把SQL标准的话语转为一个表格,如表1-2,来更好地理解一下三种异常现象。
表1-2 SQL标准定义的三种读数据异常现象
| 脏读 | 不可重复读 | 幻象 | |||
| P1 (“Dirty read”) | P2 (“Non-repeatable read”) | P3 (“Phantom”) | |||
时间 | T1 | T2 | T1 | T2 | T1 | T2 |
t0 | W(row)-Update |
| R(row) |
| R(rows)-WHERE<conditon> |
|
t1 |
| R(row) |
| W(row)-Update/Delete |
| W(rows)- Insert-<conditon> |
t2 | Rollback |
|
| Commit | R(rows)-WHERE<conditon> |
|
t3 |
|
| R(row) |
|
|
|
说明:
q 表格头两行,表明SQL标准定义的三种异常现象,分别是脏读、不可重复读、幻象。
q 表格第一列,时间值列,表明时间值在逐渐增长,即t0<t1<t2<t3。
q 对于每一种异常现象,都分为2个列,分别是两个并发的事务,各自命名为T1事务和T2事务。
q 表格中的R(row),表示读row数据对象;W(row)表示写row对象,读写操作的是同一个数据对象row;W(row)-Update/Delete后面的Update/Delete表示的写操作是DML语句中的UPDATE或DELETE语句产生的写数据库对象row的操作。
q 带有“R(rows)-WHERE<conditon>”的表示SQL语句读数据(通常是SELECT语句)带有WHERE子句,此子句的条件表达式是“<conditon>”。
q 带有“W(rows)-Update/Insert-<conditon>”的表示写操作生成的数据满足与其他事务读操作带有的相同的条件表达式“<conditon>”,且此写操作要么是UPDATE,要么是INSERT语句。
q 带有背景颜色的,表示其对应时刻,如果发生对应的操作,将引发异常现象。
q 对于脏读现象:按照时间顺序,T1事务在t0时刻对row进行了修改(更新),T2事务在t1时刻先读取了被T1修改了的row的值,但是T1在t2时刻回滚使得对row的修改失效。如果数据库引擎不支持因并发操作避免数据异常,则T2在t1时刻读到的就是T1修改后的数据,但是这个数据在现实世界中不存在,对于事务T2而言,读取了被回滚掉的数据,即事务T2发生了脏读异常现象。
q 对于不可重复读现象:事务T1在t0时刻先读取row的旧值,事务T2在t1时刻对row进行了修改(更新或删除)然后提交事务使得修改生效,此时row因更新变为了新值或因删除而不再存在。接下来,事务T1在t3时刻再次读取row对象的值但是row的值已经是新值或者不存在了。对已事务T1而言,物是(同样是读row这个对象)人非(值已经和t0时刻读到的值不同),事务T1发生了不可重复读异常现象。
q 对于幻象现象:事务T1在t0时刻带有特定条件地读取了row对象的数据,事务T2在t1时刻插入新的数据,新的数据满足与事务T1同样的条件,当事务T1在t3时刻再次以同样的条件读取数据的时候,rows对象的值已经有新加入的行(因插入而比第一次读多出了数据)。对已事务T1而言,物是(同样是读row这个对象)人非(值已经和t0时刻读到的值不同),事务T1发生了幻象异常现象。幻象又被称为幻读,即第二次读操作读取了第一次读操作没有读到的rows(一行或多行)。
在了解了SQL标准定义的三种异常现象后,回到我们在本节开始提出的问题:
在ACID的保护下,是不是数据就一定不会产生不一致的现象呢?
答案已经很明确:即使数据库系统提供ACID,除非我们使用“串行化(SERIALIZABLE)”隔离级别,否则数据在其他不同的隔离级别下还会产生数据不一致的现象。