三级封锁协议两段锁以及隔离级别
并发控制的主要方法是封锁(Locking)。就是要用正确的方式调度并发操作,使一个用户的事务在执行过程中不受其他事务的干扰,从而避免造成数据的不一致性。
封锁是使事务对它要操作的数据有一定的控制能力。封锁通常具有3个环节:第一个环节是申请加锁,即事务在操作前要对它将使用的数据提出加锁申请;第二个环节是获得锁,即当条件成熟时,系统允许事务对数据进行加锁,从而事务获得数据的控制权;第三个环节是释放锁,即完成操作后事务放弃数据的控制权。
基本的封锁类型有以下两种: 锁是事务实现并发控制隔离级别的实现方法
1.排它锁(Exclusive Locks,简称X锁)
排它锁也称为独占锁或写锁。一旦事务T对数据对象A加上排它锁(X锁),则只允许T读取和修改A,其他任何事务既不能读取和修改A,也不能再对A加任何类型的锁,直到T释放A上的锁为止。
2.共享锁(Share Locks,简称S锁)
共享锁又称读锁。如果事务T对数据对象A加上共享锁(S锁),其他事务只能再对A加S锁,不能加X锁,直到事务T释放A上的S锁为止。
在对数据进行加锁时,另外需要约定并执行一些规则和协议,其中包括何时申请锁,保持锁的时间以及何时释放等,这些规则就称为封锁协议(Locking Protocol){谁定义的?-确实找不出来,也许就是理论基础},其总共分为以下三级:
(1)一级封锁协议。一级封锁协议是事务T在修改数据之前必须先对其加X锁,直到事务结束才释放。 防止丢失更新。
(2)二级封锁协议。二级封锁协议是事务T对要修改数据必须先加X锁,直到事务结束才释放X锁;对要读取的数据必须先加S锁,读完后即可释放S锁。 防止丢失更新和脏读
T1 | T2 | ||
Slock A | |||
读A=20 unlcok A 事务还没完成,但是读完了马上释放 | |||
Xlock A | |||
A = 60 | |||
Unclock A 事务提交 | |||
SlockA | |||
A=60 不可重复读 | |||
T1事务造成了不可重复读 |
(3)三级封锁协议。三级封锁协议是事务T在读取数据之前必须先对其加S锁,在要修改数据之前必须先对其加X锁,直到事务结束后才释放所有锁。 防止丢失更新,脏读,不可重复读
相对于二级锁,Slock的范围加长了,开销自然大了。
执行了封锁协议之后,就可以克服数据库操作中的数据不一致所引起的问题。
参看图4。
从图4的情况我们看到事务T1在执行过程中独自占用并加X锁,直到处理完之后再释放锁,T2虽然也需要使用,但是在封锁协议的约束之下,T2所要求的X 锁就被拒绝,因此必须处于等待状态,直到T1释放之后,T2才获得使用的权利,这样就不会发生使用冲突,避免了数据的丢失。这里我们看到,此处实际上是执行了一级封锁协议。
下面我们看图5。
通过图5,能够清楚的看到,由于施行了封锁协议,使事务T1使用了共享锁占用A,B两块数据,这样T2需要加上的X锁就无法实现,(如果是S锁,虽然可以加上,但也不能够随便修改数据,只是读取一下数据。)当T1释放锁之后,T2就可以得到并使用锁了,这样读取的数据B仍然还是100,不影响A+B的结果,这就是可重复读取。因此我们看到,其实这里用的就是三级封锁协议
参看图6,事务T1在对数据C修改之前,先加上了X锁,修改后写回数据库,这时T2请求在C上添加S锁,因为T1加了X锁,T2只好等待,当T1因为某种原因撤销了修改的数据后,C就恢复了原来的数据100,等T1释放 X锁后T2获得C上的S锁,读到的还是C=100,因此避免了读出“脏”数据。这里使用的其实就是二级封锁协议。
两阶段封锁协议
对锁机制,保证事务可串行性的最常用协议是两阶段封锁协议。该协议要求每个事务分两个阶段提出加锁和解锁申请:
(1)增长阶段。事务可以获得锁,但不能释放锁。
(2)缩减阶段。事务可以释放锁,但不能获得锁。
一开始,事务处于增长阶段,事务根据需要获得锁。一旦该事务释放了锁,它就进入缩减阶段,不能再发出加锁请求。
两阶段封锁协议实现了事务集的串行化调度,但同时,一个事务的失败可能会引起一连串事务的回滚。为避免这种情况的发生,我们需要进一步加强对两阶段封锁协议的控制,这就是:严格两阶段封锁协议和强两阶段封锁协议。
严格两阶段封锁协议除了要求封锁是两阶段之外,还要求事务持有的所有排它锁必须在事务提交之后方可释放。这个要求保证未提交事务所写的任何数据,在该事务提交之前均以排它锁封锁,防止其他事务读取这些数据。
强两阶段封锁协议,要求事务提交之前不得释放任何锁。使用锁机制的数据库系统,要么使用严格两阶段封锁协议,要么使用强两阶段封锁协议。
两阶段封锁协议并不保证不会发生死锁,数据库系统必须采取其他的措施,预防和解决死锁问题。
数据库并发操作存在的异常情况:
1. 更新丢失(Lost update):事务 T1 读取数据 A,然后对 A 进行运算修改,最后写回数据库。如果在 T1 读取和写回数据库之间,有其他事务修改了 A 值,就造成了丢失更新,因为 T1 是在旧的数据上进行的运算。
第一类丢失更新(lost update): 在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。
第二类丢失更新(second lost updates):是不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。
说他是第二类更新,又说他其实是不可重复读的特例,实际上在隔离级别上又和不可重复读相同,大部分分类根本不提这个第二类丢失更新。
2. 脏读取(Dirty Reads):一个事务读取了另一个未提交的并行事务写的数据。事务 T1 修改了数据 A,然后事务 T2 读取了数据 A,然后事务 T1 回滚了事务。则T2读的是错误的数据。
3. 不可重复读取(Non-repeatable Reads):一个事务对同一行数据重复读取两次但是却得到了不同结果。例如在两次读取中途有另外一个事务对该行数据进行了修改并提交
4. 幻读(Phantom Reads):也称为幻像(幻影,虚读)。事务在操作过程中进行两次查询,第二次查询结果包含了第一次查询中未出现的数据(这里并不要求两次查询SQL语句相同)这是因为在两次查询过程中有另外一个事务插入数据造成的。
幻读和不可重复读可认为是同类的,但是在控制上有区别。要控制不可重复读只需要控制记录的修改,而要控制幻读则要控制记录的添加和删除。所以,隔离级别可重复读取不能禁止幻读,而串行则可以。
事务隔离级别:
为了避免上面出现几种情况在标准SQL规范中定义了4个事务隔离级别,不同隔离级别对事务处理不同 。
1. 未授权读取(Read Uncommitted):也称未提交读。防止更新丢失(这不对应一级锁吗),如果一个事务已经开始写数据则另外一个数据则不允许同时进行写操作但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。事务隔离的最低级别,仅可保证不读取物理损坏的数据。与READ COMMITTED 隔离级相反,它允许读取已经被其它用户修改但尚未提交确定的数据。
2. 授权读取(Read Committed):也称提交读。1之上防止脏读取(这不对应二级锁吗)。这可以通过“瞬间共享读锁”和“排他写锁”实现,读取数据的事务允许其他事务继续访问该行数据,但是未提交写事务将会禁止其他事务访问该行。SQL Server 默认的级别。在此隔离级下,SELECT 命令不会返回尚未提交(Committed) 的数据,也不能返回脏数据。
3. 可重复读取(Repeatable Read):2之上防止不可重复读取(这不对应三级锁吗)。但是有时可能出现幻影数据,这可以通过“共享读锁”和“排他写锁”实现,读取数据事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。在此隔离级下,用SELECT 命令读取的数据在整个命令执行过程中不会被更改。此选项会影响系统的效能,非必要情况最好不用此隔离级。
三级封锁协议并不能阻止幻读,修改的不能再被读取,但是新增(删除)的记录数可以统计。
4. 串行(Serializable):也称可串行读(这不对应两段锁吗)。提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过 “行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作事务访问到。事务隔离的最高级别,事务之间完全隔离。如果事务在可串行读隔离级别上运行,则可以保证任何并发重叠事务均是串行的。
LU丢失更新 | DR脏读 | NRR非重复读 | SLU二类丢失更新 | PR幻像读 | |
未提交读 RU | Y | Y | Y | Y | Y |
提交读 RC | N | N | Y | Y | Y |
可重复读 RR | N | N | N | N | Y |
串行读 S | N | N | N | N | N |
ORACLE的默认事务级别:READ COMMITTED
ORACLE支持的事务隔离级别:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET TRANSACTION ISOLATION LEVEL READ ONLY;
少数数据库默认的隔离级别为Repeatable Read, 如MySQL InnoDB存储引擎
即使是最低的级别,也不会出现 第一类 丢失 更新问题 .
查看InnoDB系统级别的事务隔离级别:
mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)
查看InnoDB会话级别的事务隔离级别:
mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)
修改事务隔离级别:
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
InnoDB的可重复读隔离级别和其他数据库的可重复读是有区别的,不会造成幻象读(phantom read)。
Oracle对隔离级别的支持:
Oracle 明确地支持READ COMMITTED(读已提交)和SERIALIZABLE(可串行化)隔离级别,因为标准中定义了这两种隔离级别。不过,这还不是全部。SQL标准试图 建立多种隔离级别,从而允许在各个级别上完成的查询有不同程度的一致性。REPEATABLE READ(可重复读)也是SQL标准定义的一个隔离级别,可以保证由查询得到读一致的(read-consistent)结果。在SQL标准的定义 中,READ COMMITTED不能提供一致的结果,而READ UNCOMMITTED(读未提交)级别用来得到非阻塞读(non-blocking read)。
不 过,在Oracle中,READ COMMITTED则有得到读一致查询所需的所有属性。在其他数据库中,READ COMMITTED查询可能(而且将会)返回数据库中根本不存在的答案(即实际上任何时间点上都没有这样的结果)。另外,Oracle还秉承了READ UNCOMMITTED的“精神”。(有些数据库)提供脏读的目的是为了支持非阻塞读,也就是说,查询不会被同一个数据的更新所阻塞,也不会因为查询而阻 塞同一数据的更新。不过,Oracle不需要脏读来达到这个目的,而且也不支持脏读。但在其他数据库中必须实现脏读来提供非阻塞读。
除 了4个已定义的SQL隔离级别外,Oracle还提供了另外一个级别,称为READ ONLY(只读)。READ ONLY事务相对于无法在SQL中完成任何修改的REPEATABLE READ或SERIALIZABLE事务。如果事务使用READ ONLY隔离级别,只能看到事务开始那一刻提交的修改,但是插入、更新和删除不允许采用这种模式(其他会话可以更新数据,但是READ ONLY事务不行)。如果使用这种模式,可以得到REPEATABLE READ和SERIALIZABLE级别的隔离性。
所以,Oracle对隔离级别的支持如下:
1.SERIALIZABLE:支持
2.READ ONLY:Oracle特有的级别,利用它来实现对REPEATABLE READ的支持
3.READ COMMITTED:支持
4.READ UNCOMMITTED:不明确且不完全地支持
参见:
http://seaizon.iteye.com/blog/571139
http://wenku.baidu.com/view/2f89710879563c1ec5da7130.html
http://yjhexy.iteye.com/blog/658706