在单用户的数据库中,用户可以修改数据,而不用担心其他用户在同一时间修改相同的数据。但是在一个多用户的数据库中,多个事务内的语句可以同时更新相同的数据。注意,同时执行的多个事务必须产生有意义且一致的结果。
01、并发性与一致性
1.概念
在单用户的数据库中,用户可以修改数据,而不用担心其他用户在同一时间修改相同的数据。但是在一个多用户的数据库中,多个事务内的语句可以同时更新相同的数据。注意,同时执行的多个事务必须产生有意义且一致的结果。因此,多用户数据库必须提供以下两个功能。
数据并发性:确保多个用户可以同时访问数据。
数据一致性:确保每个用户看到数据的一致的视图,包括可以看到用户自己的事务所做的更改和其他用户已提交的事务所做的更改
2.不一致性问题
并发会产生多个事务同时存取同一数据的情况。可能存取和存储不正确的数据,破坏事务的一致性和数据库的一致性。归纳起来共有以下3个主要问题。
(1)脏读:在事务中,读到了其他事务没有提交的记录,如图14-1所示。
分析:
串行化结果:x:=140
并型:R1(a),W1(A),R2(A),ROLLBACK1,W2(A),COMMIT2
结果:x:=190 错误
问题:第4步,A2读了A1写后未提交的脏数据。
(2)不可重复读:在事务中,即使查询条件相同,下次返回的记录与上次返回记录也不一样,其不一样体现在记录被修改或记录被删除,如图14-2所示。
分析:
串行化结果:X:=190
并行:R1(A),R2(A),S1(A),COMMIT,W2(A),COMMIT2。
结果,X:=140,错误。
问题:第2,6时间点,执行第6时间点前X已经不是第2时间点读出的100,已经是140了。A1对A2正在使用的记录数据做了修改。
区别:第2时间点中A2没有读A1修改但尚未提交的脏数据(不是脏读);第七时间点中A2没有写A1修改且尚未提交的脏数据(不是脏写)。
(3)幻影读:在事务中,即使查询条件相同,下次返回的记录与上次返回记录也不一样,其不一样体现在新增加了记录,如图14-3所示。
分析:
串行化结果:两个事务都满足规则,则返回YES。
并行:同一事物中相同的两次查询结果都一样,结果集记录的个数却增加一个。
Oracle中,使用谓词锁避免幻影读,即满足select语句中where谓词的记录都不能被insert,其他可以。
3.解决办法
要想解决脏读、不可重复读、幻影读等不一致的问题,就需要提高事务的隔离级别。
为了描述事务并发运行时的一致性行为,研究人员定义了一种事务隔离模型,称之为序列化(Serializability)。这种可串行化事务操作使得它看起来似乎没有其他用户在操作数据。虽然这种序列化机制在一般情况下是可用的,但在并发要求高的场景,它会严重影响系统的吞吐能力。即事务的隔离级别越高,并发能力也就越低。所以,一般情况下,需要在事务隔离级别与性能间进行取舍。
从运维的角度来看,提高事务隔离级别的解决办法可以总结如下所述。
(1)在通常情况下,程序员无须考虑每个事务内部的处理细节来避免并发异常,而是通过控制隔离级别来保证不出现相关的异常。
(2)程序员可以采用以下两种办法尽量避免可能的并发错误。
使用尽量少的SQL,直接修改数据。
可以使用手动加锁等方式预先申请资源。
02、隔离机制
为了兼顾并发效率和异常控制,在标准SQL规范中,定义了如图14-4所示的四个事务隔离级别以及各隔离级别对各种异常的控制能力。
Oracle数据库提供了Read committed(默认级别)和Serializable两种隔离级别,同时还支持只读模式。
1.Read Committed事务隔离级别
在此级别中,事务中查询到的数据都是在此查询前已经提交的。这种隔离级别避免了读取脏数据。
然而数据库并不阻止其他事务修改一个所读取的数据,其他事务可能在查询执行期间修改。
因此 ,一个事务运行同样的查询两次,可能发生不可重复读和幻影读的情况。
2.Serializable事务隔离级别
简单地说,Seriaizable就是使事务看起来像是一个接着一个地顺序地执行。
仅仅能看见在本事务开始前由其他事务提交的更改和在本事务中所做的更改。
保证不会出现非重复读和幻象。Seriaizable隔离级别提供了Read-Only事务所提供的读一致性(事务级的读一致性),同时又允许执行数据操纵类型的操作。
3.Read-Only事务隔离级别
只读隔离级别和序列化隔离级别很像,只是在只读事务中,除了用户sys外。不允许有修改操作,
因此只读事务不会有ORA-08177错误,只读事务在产生一个报告时很有效。
03、锁机制
事务之间的并发控制实际是通过锁实现的,锁是用来预防事务之间访问相同数据时的破坏性交互(如错误的更新数据等)的一种机制,在维护数据库并发性与一致性方面扮演了一个重要角色。
1.自动锁
Oracle数据库在执行SQL的时候会自动获取所需要的锁,因此用户在应用设计的时候只需要定义恰当的事务级别,不需要显示锁定任何资源。(即使Oracle提供了手动锁定数据的方法,用户不会用到。)
2.Oracle锁模式
Oracle自动使用最低的限制级别去提供最高程度的并发。Oracle中有以下两种类型的锁。
(1)排他锁(X):这种类型是阻止资源共享的,一个事务获取了一个排他锁,则这个事务锁定期间只有这个事务可以修改这个资源。
(2)共享锁(S):这种类型是允许资源共享的,多个事务可以在同一资源上获取共享锁。
假设一个事务使用select ... for update(其他DML操作也一样)来选择表中的一行,则这个事务会获取该行的排他行锁以及所在表的共享表锁。
行锁允许其他事务修改除该行的其他行,而表锁阻止其他事务修改表结构。
3.Oracle封锁粒度
DML锁可以防止多个互相冲突的DDL,DML操作对数据的破坏性。DML语句自动获取两种类型的锁:Row locks(TX)和Table locks(TM),封锁粒度如图14-5所示。
4.意向锁
表是由行组成的,当向某个表加锁时,一方面需要检查该锁的申请是否与原有的表级锁相容;另一方面,还要检查该锁是否与表中的每行上的锁相容。
比如一个事务要在一个表上加S锁,如果表中的一行已被另外的事务加了X锁,那么该锁的申请也应被阻塞。
如果表中的数据很多,逐行检查锁标志的工作量将很大,系统的性能也会受到影响。
为了解决这个问题,可以在表级引入新的锁类型来表示其所属行的加锁情况,于是引出了“意向锁”的概念
意向锁的含义是如果对一个节点加意向锁,则说明该节点的下层节点正在被加锁;当对任一节点加锁时,必须先对它的上层节点加意向锁。
例如对表中的任一行加锁时,必须先对它所在的表加意向锁,然后再对该行加锁。
这样一来,当事务对表加锁时,就不再需要检查表中每行记录的锁标志位了,系统效率得以大大提高。
锁有S锁和X 锁两种基本的锁类型,可以自然地派生出两种意向锁。
意向共享锁(Intent Share Lock,简称 IS 锁):如果要对一个数据库对象加S锁,首先要对其上级节点加IS锁,表示它的后裔节点拟(意向)加S锁。
意向排它锁(Intent Exclusive Lock,简称 IX 锁):如果要对一个数据库对象加X 锁,首先要对其上级节点加 IX锁,表示它的后裔节点拟(意向)加X锁。
另外,基本的锁类型(S、X)与意向锁类型(IS、IX)之间还可以组合出新的锁类型,理论上可以组合出4种,即:S+IS,S+IX,X+IS,X+IX。
但稍加分析不难看出,实际上只有 S+IX 有新的意义,其他三种组合都没有使锁的强度得到提高(即:S+IS=S,X+IS=X,X+IX=X,这里的“=”指锁的强度相同)。
所谓锁的强度是指对其他锁的排斥程度。
这样又可以引入一种新的锁的类型——共享意向排它锁(SharedIntent Exclusive Lock,简称 SIX 锁):如果对一个数据库对象加 SIX 锁,表示对它加 S 锁,再加IX 锁,即 SIX=S+IX。
例如,若事务对某个表加 SIX 锁,则表示该事务要读整个表(所以要对该表加S 锁),同时会更新个别行(所以要对该表加 IX锁)。
这样数据库对象上所加的锁类型就可能有5 种:即S、X、IS、IX、SIX。
5.TM锁的相容矩阵
6.手工锁
Oracle数据库自动执行锁定,以确保数据并发性、数据完整性和与数据读取一致性。
但是也可以使用手动锁覆盖默认的锁机制。例如,事务中包含如下语句时会覆盖Oracle的默认锁:
settransaction isolation level lock table, select... for update
04、项目案例
1●Read Commited隔离级别实例
事务1(可以是Read committed或Serializable)与事务2(Read Committed)的典型交互,称之为Lost Update(丢失更新),具体如下操作步骤所示。
2●Serializable事务隔离级别实例
下面所示是一个序列化事务是如何与其他事务交互的。
如果一个序列化任务不去尝试修改其他事务在序列化事务开始后提交的数据,那么Serialized Access问题就可以避免了。
3●提交读与序列实例
1.提交读
这是 Oracle默认的事务隔离级别。
Oracle中任何事务中的任何一条语句都必须遵从语句级的读一致性。
在这种一致性的前提下,保证不会脏读,但可出现非重复读和幻象。
从语句和事务两个层次分析,提交读具有下面两个特点:
语句开始执行时拍一个快照,在该语句的执行过程中,所有的数据以快照为准。
在事务处理过程中,一个事务不能看到另外一个未提交事务的修改。
语句执行过程中,看不到别的事务的任何修改。而对于“事务处理过程中,一个事务不能看到另外一个事务的未提交的修改”,通过实例来说明这个问题,如下所示。
2.序列化
序列化(Serializable)可以使事务看起来像是一个接着一个顺序地执行。
事务在开始前对数据库中的所有数据拍一个快照,在事务执行过程中仅能看到这个快照中的数据(仅仅能看见在本事务开始前由其他事务提交的更改)和在本事务中所做的更改。
下面所示的是在设置序列化的隔离级别后,即使A事务提交,B事务仍然看不到A事务对数据的修改。
4●行锁定实例
在下面的更新语句中,email和phone_number都是原始的值,这样更新,能够避免上面例子中提到的丢失更新的问题。
下表所示的是当两个会话在相同时间更新employees表中相同的行时的执行顺序。
Oracle数据库在执行SQL时会自动获取所需要的锁,因此用户在应用设计时只需要定义恰当的事务级别即可,不需要显示锁定任何资源。