0%

事务隔离性简介

本文主要讲解事务四大特性ACID中的隔离性,这里简单介绍什么是隔离性,接着介绍隔离性级别以及每种隔离性会带来的相应的读的问题,最后简单介绍数据库是如何实现事务的隔离性。

  1. 什么是隔离性
  2. 隔离性级别
  3. 隔离性级别的实现方式简介

什么是隔离性

在数据库系统中会存在多个事务可能并发执行,系统保证,对于任何一对事务T1和T2,在T1看来,T2或者在T1开始之前就已经完成执行,或者在T1完成之后开始执行。因此,每个事务都感觉不到系统中有其他事务在并发的执行。这就是事务的隔离性

隔离性级别

  • 可串行化(serializable):通常保证可串行化调度。
  • 可重复读(repeatable read):只允许读取已提交数据,而且在一个事务俩次读取一个数据项期间,其他事务不得更新该数据。但该事务不要求与其他事务可串行化。
  • 已提交度(read committed):只允许读取已提交数据,但不要求可重复读。比如,在事务俩次读取一个数据项期间,另一个事务更新了该数据并提交。
  • 未提交读(read uncommitted):允许读取未提交数据。

以上所有的隔离级别都不允许脏写:如果一个数据项已经被另外一个尚未提交或终止的事务写入,则不允许对该数据项执行写操作。另外上面四中隔离性界别,从下到上,性能越来越低,并发度越来越差。

下面介绍每种隔离级别带来的问题,

脏读

发生在未提交读隔离级别

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

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

举例说明:

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

事务1 事务2
SELECT age FROM users WHERE id = 1;
/* will read 20 */
- UPDATE users SET age = 21 WHERE id = 1;
/* No commit here */
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)”现象。

事务一 事务二
SELECT * FROM users WHERE id = 1;
- UPDATE users SET age = 21 WHERE id = 1;
COMMIT;
SELECT * FROM users WHERE id = 1;
COMMIT; `

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

幻读

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

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

举例说明:

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

事务一 事务二
SELECT * FROM usersWHERE age BETWEEN 10 AND 30;
- INSERT INTO users VALUES ( 3, 'Bob', 27 );
COMMIT;
SELECT * FROM usersWHERE age BETWEEN 10 AND 30;

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

隔离性级别的实现方式简介

在数据库中使用并发控制机制来保证事务的隔离性,使用并发控制的目的是为了提高事务的并发性,从而提数据库的整体性能。具体的实现有以下几种形式。

一个数据库可以封锁其访问的数据项,而不用封锁整个数据库。这种策略下,事务必须在足够长的时间内持有锁来保证可串行化。但是这一周期又要足够短致使不会过度影响性能。在封锁协议中,俩阶段封锁协议就是一种简单且广泛的确保可串行化的封锁协议:简单的说就是,俩阶段封锁要求一个事务封锁有俩个阶段,一个阶段只获得锁但不释放锁,第二个阶段只释放锁但不获得锁。

当我们有俩种锁,则封锁的结果可以将进一步得到改善:读锁和写锁,

读写锁的概念很平常,当你在读取数据的时候,应该先加读锁,读取完之后的某个时间再解开读锁,那么加了读锁的数据,应该需要有什么特性呢,应该只能读,不能写,因为加了读锁,说明有事务准备读取这个数据,如果被别的事务重写这个数据,那数据就不准确了。所以一个事务给这个数据加了读锁,别的事务也可以对这个数据加读锁,因为大家都是只读不写。

写锁则具有排他性(exclusive lock),当一个事务准备对一个数据进行写操作的时候,先要对数据加写锁,那么数据就是可变的,这时候,其他事务就无法对这个数据加读锁了,除非这个写锁释放。

时间戳

这种是为每一个事务分配一个时间戳,通常当他开始的时候。对于每个数据项,系统维护俩个时间戳。数据项的读时间戳记录读该数据项的事务的最大时间戳。数据项的写时间戳记录写入该数据项的时间戳。时间戳用来确保在访问冲突情况下,事务按照事务的时间戳的顺序来访问数据项。当不能访问时,事务将会终止,并且分配一个新的时间戳重新开始。

多版本和快照隔离

通过维护数据项的多个版本,一个事务允许读取一个旧版本的数据项,而不是被另一个未提交或者在串行化序列中应该排在后面的事务写入的新版本的数据项。有许多的版本控制并发控制技术,其中一种应用比较广泛的就是快照隔离。

在快照隔离中,我们可以想象每个事务开始时有其自身的数据库版本或者快照。他从这个私有的版本中读取数据,因此和其他事务所做的更新隔离开。如果事务更新数据库,更新只出现在其私有版本中,而不是实际的数据库版本中。当事务提交时,和更新有关的信息将被保存,使得更新被写入真正的数据库。当一个事务进入部分提交状态后,只有在没有其他并发事务修改了该事务想要更新的数据项的情况下,事务进入提交状态。而不能提交的事务则终止。

快照隔离可以保证读数据的尝试永远无需等待(不向封锁情况)。只读事务不会中止,只有修改数据的事务有微小的中止风险。由于每个事务读取他自己的数据版本或快照,因此读数据不会导致此后其他事务的更新尝试被迫等待(不想封锁情况)。因为大部分事务是只读的(并且大多数其他事务读数据情况多余更新),所以这是与锁相比往往带来性能改善的主要原因。

总结

本篇文章简单总结了事务不同隔离性级别会带来的问题,以及事务隔离性的实现方式。后面会在写几篇文章来说明事务隔离性实现的具体细节。

参考

  1. 数据库系统概念
  2. 数据库的读现象浅析