数据库是一个共享的数据资源。为了提高使用效率,数据库基本上都是多用户的,即允许多个用户并发地访问数据库中的数据,如飞机订票数据库系统、银行储蓄数据库系统等。在这样的系统中,同一时刻并行运行的事务可多达数百个。如果对这种并发访问不加以控制,就会破坏数据的一致性,出现丢失修改、脏读和不可重复读这些问题,如图20.1所示。
丢失修改 |
脏读 |
不可重复读 |
|||
T1 |
T2 |
T1 |
T2 |
T1 |
T2 |
读A=16 |
读A=16 做A=A-4 写回A=12 |
读A=16 |
|||
读A=16 |
读A=12 |
读A=16 做A=A-2 写回A=14 |
|||
做A=A-3 写回A=13 |
撤销 |
读A=14 |
|||
做A=A-1 写回A=15 (数据库中最后为15) |
(A恢复为16) |
(A为12,与数据库中的16不相同) |
图20.1 并发访问带来的3个问题
一、丢失修改(lost update)
两个事务T1和T2读入同一个数据,并修改,T2提交的修改结果覆盖了T1提交的修改结果,导致T1的修改结果丢失。
例如,如图20.1所示,考虑飞机订票系统中的一个操作序列:
1) 甲售票点(T1事务)读出某航班的机票余额A,此时为16张。
2) 乙售票点(T2事务)读出同一航班的机票余额A,此时也为16张。
3) 甲售票点卖出3张机票,将机票余额A修改成13张,写回数据库。
4) 乙售票点卖出1张机票,将机票余额A修改成15张,写回数据库。
实际上甲、乙两个售票点共卖出4张机票,可数据库中最后显得仅仅是卖出了1张机票。显然,这是由于并发访问所造成的。在上面这种操作序列下,第4)步中乙售票点的修改结果覆盖了第3)步中甲售票点的修改结果,所以导致了这个错误。这就是著名的并发访问中的飞机订票问题。
二、脏读(dirty read)
事务T1更改某一数据,并写入数据库,事务T2读取同一数据,但事务T1由于某种原因被撤销,此时T1更改过的数据恢复原来的值,使T2读取到的值与数据库中的值不相同,只是操作过程中的一个过渡性的、不再需要的、脏的数据,如图20.1所示。
三、不可重复读(non-repeatable read)
事务T1读取数据后,事务T2执行更改操作,使T1无法再现前一次读取的结果。不可重复读包括如下几种情况。
$$ 事务T1读取某一数据之后,事务T2对其进行更改,使T1再次读取该数据时,读取的结果与上次不同,如图20.1所示。
$$ 事务T1按一定条件读取某些数据记录之后,事务T2删除了其中部分记录,使T1按相同的条件再次读取这些数据记录时,发现某些记录不在了。
$$ 事务T1按一定条件读取某些数据记录之后,事务T2插入了一些记录,使T1按相同的条件再次读取这些数据记录时,发现增加了某些记录。
后两种关于记录的不可重复读现象也被称为幻像读(phantom read)现象。
产生上述几种问题的主要原因是由于违反了事务的隔离性原则,发生了事务之间的相互影响。
为了保证并发事务执行的正确性,必须要有一定的机制来保证一个事务的执行不受另一个事务的影响。例如,如果在飞机订票例子中,甲售票点在读出A之后就控制住A,使乙售票点此时无法读取A,直到甲售票点更改 A并将A=13写回数据库后再解除对A的控制为止。这样一来,乙售票点的操作就是在甲售票点的基础上再进行的,就会得出正确的结果A=12,尤其是不会覆盖甲售票点的更改。
“注意”上述问题中的不可重复读、脏读是问题或现象但并非就是错误,在某些情况下是可以接受的。例如,有的统计工作涉及的数据量很大,读到一些脏数据、多读到几行或少读到几行对统计精度没多大影响。为了减少系统开销,可以降低要求。
来源:https://www.cnblogs.com/pyq228/archive/2012/05/26/2519447.html