数据库读现象之脏读、不可重复读、幻读
一 数据库读现象
数据库管理软件的“读现象”指的是当多个事务并发执行时,在读取数据方面可能碰到的问题,包括有脏读、不可重复读和幻读。
ps:对于一些数据库管理软件会自带相应的机制去解决脏读、不可重复读、幻读等问题,因为这些自带的机制,下述的一些实验现象可能在某一数据库管理软件的默认机制下并不成立,即我们并不能在所有数据库管理软件中看到所有的读现象。所以此处我们暂且抛开具体的某个数据库管理软件的默认机制的干扰,暂时假设没有附加任何机制为前提,单纯地去理解数据库的读现象。
1.1、脏读 (dirty read)
脏读就是当在某一个事务之中,出现修改之后的查询,得到的数据是未提交的数据库数据。
事务T2更新了一行记录的内容,但是并没有提交所做的修改。
事务T1读取更新后的行,然后T1执行回滚操作,取消了刚才所做的修改。此时T1所读取的行就无效了,称之为脏数据。
举例
事务一 | 事务二 |
---|---|
/* 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的数据了。所以,事务一读到了一条脏数据。
1.2、不可重复读取 (nonrepeatable read)
当我们读取的数据在其他事务之中被修改了,这个时候本事务中没有及时读取正确内容,即称之为不可重复读现象。
事务T1读取一行记录,紧接着事务T2修改了T1刚才读取的那一行记录并且提交了。
然后T1又再次读取这行记录,发现与刚才读取的结果不同。这就称为“不可重复”读,因为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 concurrencycontrol, or lock-based READ COMMITTED */ |
|
/* Query 1 */ SELECT * FROM users WHERE id = 1; COMMIT; /*lock-based REPEATABLE READ */ |
在这个例子中,事务2提交成功,因此他对id为1的行的修改就对其他事务可见了。但是事务1在此前已经从这行读到了另外一个“age”的值。
1.3、幻像读取 (phantom read)
当在一个事务中读取某一个范围之内的数据时,另一个事务修改了在本事务查询范围之内的数据,此时该事务读取不到新修改的数据,这种现象称之为幻读。
幻读(phantom read)”是不可重复读(Non-repeatable reads)的一种特殊场景:
当事务没有获取范围锁的情况下执行SELECT … WHERE操作有可能会发生“幻影读(phantom read)”。
事务T1读取或修改了指定的WHERE子句所返回的结果集。
然后事务T2新插入一行记录,这行记录恰好可以满足T1所使用的查询条件中的WHERE 子句的条件。
然后T1又使用相同的查询再次对表进行检索,但是此时却看到了事务T2刚才插入的新行或者发现了处于WHRE子句范围内,但却未曾修改过的记录。就好像“幻觉”一样,因为对T1来说这一行就像突然出现的一样。
一般解决幻读的方法是增加范围锁RangeS,锁定检锁范围为只读,这样就避免了幻读。
举例
当事务1两次执行SELECT … WHERE检索一定范围内数据的操作中间,事务2在这个表中创建了(如INSERT)了一行新数据,这条新数据正好满足事务1的“WHERE”子句。
事务一 | 事务二 |
---|---|
/* Query 1 */ SELECT * FROM usersWHERE age BETWEEN 10 AND 30; |
|
/* Query 2 */ INSERT INTO users VALUES ( 3, 'Bob', 27 ); COMMIT; |
|
/* Query 1 */ SELECT * FROM usersWHERE age BETWEEN 10 AND 30; |
在这个例子中,事务一执行了两次相同的查询操作。但是两次操作中间事务二向数据库中增加了一条符合事务一的查询条件的数据,导致幻读。
二 解决方案
其实,脏写、脏读、不可重复读、幻读,都是因为业务系统会多线程并发执行,每个线程可能都会开启一个事务,每个事务都会执行增删改查操作。然后数据库会并发执行多个事务,多个事务可能会并发地对缓存页里的同一批数据进行增删改查操作,于是这个并发增删改查同一批数据的问题,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。
所以这些问题的本质,都是数据库的多事务并发问题,那么为了解决多事务并发带来的脏读、不可重复读、幻读等读等问题,数据库才设计了锁机制、事务隔离机制、MVCC 多版本隔离机制,用一整套机制来解决多事务并发问题,下面我们来分别介绍一下它们