[数据库事务与锁]详解二: 数据库的读现象浅析

 

注明: 本文转载自http://www.hollischuang.com/archives/900

“读现象”是多个事务并发执行时,在读取数据方面可能碰到的状况。先了解它们有助于理解各隔离级别的含义。其中包括脏读、不可重复读和幻读。

脏读

脏读又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的。

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交(commit)到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

举例说明:

在下面的例子中,事务2修改了一行,但是没有提交,事务1读了这个没有提交的数据。现在如果事务2回滚了刚才的修改或者做了另外的修改的话,事务1中查到的数据就是不正确的了。

事务一事务二
/* Query 1 */

SELECT age FROM users WHERE id = 1;

/* will read 20 */
 
 
/* Query 2 */
 
UPDATE users SET age = 21 WHERE id = 1;

/* No commit here */

/* Query 1 */

SELECT age FROM users WHERE id = 1;
/* will read 21 */
 
 
ROLLBACK;

/* lock-based DIRTY READ */

在这个例子中,事务2回滚后就没有id是1,age是21的数据了。所以,事务一读到了一条脏数据。

不可重复读

不可重复读,是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。这是由于查询时系统中其他事务修改的提交而引起的。比如事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。

一种更易理解的说法是:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。

举例说明:

在基于锁的并发控制中“不可重复读(non-repeatable read)”现象发生在当执行SELECT 操作时没有获得读锁(read locks)或者SELECT操作执行完后马上释放了读锁; 多版本并发控制中当没有要求一个提交冲突的事务回滚也会发生“不可重复读(non-repeatable read)”现象。

事务一事务二
/* Query 1 */

SELECT * FROM users WHERE id = 1;
 
 
/* Query 2 */
 
UPDATE users SET age = 21 WHERE id = 1;

COMMIT;


/* in multiversion concurrency
control, or lock-based READ COMMITTED */

/* Query 1 */

SELECT * FROM users WHERE id = 1;

COMMIT; 

/*lock-based REPEATABLE READ */
 

在这个例子中,事务2提交成功,因此他对id为1的行的修改就对其他事务可见了。但是事务1在此前已经从这行读到了另外一个“age”的值。

幻读

幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样.一般解决幻读的方法是增加范围锁RangeS,锁定检锁范围为只读,这样就避免了幻读。  

幻读(phantom read)”是不可重复读(Non-repeatable reads)的一种特殊场景:当事务没有获取范围锁的情况下执行SELECT … WHERE操作可能会发生“幻影读(phantom read)”。

举例说明:

当事务1两次执行SELECT … WHERE检索一定范围内数据的操作中间,事务2在这个表中创建了(如INSERT)了一行新数据,这条新数据正好满足事务1的“WHERE”子句。

事务一事务二
/* Query 1 */

SELECT * FROM users
WHERE age BETWEEN 10 AND 30;
 
 
/* Query 2 */
 
INSERT INTO users VALUES ( 3, 'Bob', 27 );

COMMIT;

/* Query 1 */

SELECT * FROM users
WHERE age BETWEEN 10 AND 30;
 

在这个例子中,事务一执行了两次相同的查询操作。但是两次操作中间事务二向数据库中增加了一条符合事务一的查询条件的数据,导致幻读。

解决方案

要想解决脏读、不可重复读、幻读等读现象,那么就需要提高事务的隔离级别。但与此同时,事务的隔离级别越高,并发能力也就越低。所以,还需要读者根据业务需要进行权衡。