13、数据并发性和数据一致性
这篇文章解释了在一个多用户的数据库环境中oracle是如何维护数据一致性的
这篇文章包含下面的内容:
*在多用户环境下的数据并发性和数据一致性的介绍
*oracle是如何管理数据的并发和保证数据的一致
*oracle是如何锁数据的
*oracle闪回查询的介绍
在多用户环境下的数据并发性和数据一致性的介绍
在单用户的数据库中,用户可以不用担心其他用户在同一个时间点上修改与自己修改相同的数据库中的数据而可以放心的去修改数据。但是,在一个多用户数据库中,在多个并发的事务的语句可以同时更新同一条数据。在同一个时间点上执行的事务需要产生有意义的并且是具有一致性的结果。因此,数据并发性和数据一致性的控制在多用户数据库中是至关重要的。
*数据并发性:意味着许多用户可以在同一个时间点上能够访问数据;
*数据一致性:意味着每个用户看到的数据是保持一致性的视图,包括用户自己的事务或者是其他用户的事务所做的可见的数据变更;
当多个事务在同一时刻运行时为了描述一致的事务行为,数据库研究者定义了一个事务隔离模型叫串型化。事务行为的串型模式试图确保事务以这样的模式运行,这种模式看起来是这些事务是在每次一个事务被执行或者是连续地被执行,而实质上不是并发地被执行。
虽然事务之间的隔离的程度一般是值得要的,但是在这个模式下运行的许多应用程序可能严整地影响到应用程序的吞吐量。并发运行的事务的完全隔离会意味着当一个表被一个事务查询时则另一个事务就不能向该表中做插入数据的操作。简而言之,在真实的情况下的考虑需要在理想的事务隔离和性能之间做一个折衷。
Oracle提供两种隔离级别,一个向应用程序开发者提供保护数据一致性的操作的模式和一个提供高性能。
可预防的现象以及事务隔离级别
ANSI/ISO SQL(SQL92)标准中对在事务处理的整个过程中影响的程度不同定义了四个级别的事务隔离。这些隔离级别是在下面的所列出的三个现象在并发运行的事务之间必须被防止的情况下被定义的。
三个被防止的现象是:
*脏读:一个事务读取了被另一个事务写入但还没有提交的数据;
*不可重复读:一个事务重新读取自身先前读的数据,发现另一个已经提交的事务已经修改或者删除了这个数据;
*幻影读:一个事务重新执行了一个查询时,发现返回了满足查询条件的记录集中存在其他已提交的事务插入了满足条件的新记录;
SQL92根据一个运行在一个特定的隔离级别上的事务允许经历的现象定义了四个离级别:
表13-1 依据隔离级别需要防止的读现象
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
未提交读 | 可能 | 可能 | 可能 |
已提交读 | 不可能 | 可能 | 可能 |
重复读 | 不可能 | 不可能 | 可能 |
串形读 | 不可能 | 不可能 | 不可能 |
Oracle提供以提交读和串形读隔离级别,以及SQL92中没有包括的只读模式。提交读是默认使用的隔离级别。
锁机制介绍
一般,多用户数据库使用一些数据锁的形式来解决和数据并发和数据一致以及数据完整性相关的问题。锁是一个在访问相同资源的事务之间阻止相互之间有破坏性活动的一种机制。
资源包含两个常规的对象类型:
*用户对象,比如:表和行(结构和数据);
*对用户不可见的系统对象,比如涝灾内存中的共享数据结构和数据字典行;
Oracle是如何管理数据的并发和一致性
在多用户环境下oracle是采用一个多版本的一致性模型和各种类型的锁和事务来维护数据的一致性。在这部分讨论下面的话题:
*多版本一致性控制
*语句级别的读一致性
*事务级别的读一致性
*在RAC中的读一致性
*oracle的隔离级别
*已提交读取和串行读取的比较
*隔离级别的选择
多版本数据一致性的控制
Oracle自动地对一个查询提供读一致性以便那个查询查询出来的所有的数据来自一个单独的时间点上(语句级别的读一致)。Oracle也提供在一个事务中的所有的查询也是读一致的(事务级别的读一致)。
Oracle使用它的回滚段维护的信息来提供这些一致的视图。回滚段中包含了那些已经被还没有提交的或者已经被提交的事务所改变的数据的旧数据。图13-1显示oralce是如何使用在回滚段中的数据来提供语句级别的读一致。
图13-1 事务和读一致
当一个查询进入执行阶段,当前的系统变更号(SCN)就已经决定下来了。在图13-1中,这个SCN是10023。当查询在读取数据块时,只有那些写有被遵守的SCN的数据块才被使用。而那些带有变更数据的数据块(最近SCNs)才被来自回滚段中的数据重构,并且重构的数据为该查询所应该返回的数据。因此,每个查询返回的所有已提交的数据都是依据查询开始执行的时间点上所记录的SCN相同的所有数据。发生在一个查询执行过程中的其他事务的变更是观察不到的,保证为每个查询返回一致性的数据即可。
语句级别的读一致
Oracle强迫语句级别的读一致。这个保证了一个单独的查询返回的所有的数据是来自一个单独的时间点上—查询开始的时间点。因此,一个查询从来看不到脏数据或者看不到在查询语句执行的过程中提交的事务所做的数据任何数据变更。当查询执行在进行时,只有在查询开始之前提交的数据对于该查询才是可见的。这个查询看不到在查询语句执行开始之后的提交的数据变化。
为每个查询提供一个一致的结果集,保证数据一致性,对用户方面没有行动要求。带有子查询的select ,insert 的SQL语句以及update和delete或者明确得或者暗含的所有的查询语句都返回一致的数据。这些语句中的每一个使用一个查询来决定哪些数据将被影响到(分别是select ,insert,update或者delete这些语句)。
一个select语句是一个明确的查询并且可以有嵌套查询或者是连表查询。一个insert语句能够使用嵌套查询。Update和delete语句可以使用where语句或者子查询来影响一张表中的仅有的一些行数据而不是所有行数据。
使用在insert,update以及delete语句中的查询被保证有一致的结果集。但是,它们看不见被DML语句自身所做的数据变更。换句话说,在这些操作中的查询只看见在操作开始做变更之前的存在的数据。
注意:如果一个查询列表中包含一个函数,然后数据库在PL/SQL函数代码中运行的SQL语句级别应用语句级别的读一致,而不是在父的SQL级别的上应用语句级别的读一致。例如:一个函数会访问一张表,这张表的数据已经被其他用户做了改变并且提交。对于在这个函数中的每个select语句的执行,一个新的读一致快照将被建立。
事务级别的读一致
Oracle也 提供事务级别的读一致性。当一个事务运行在串行化模式下,所有的数据访问反映的是该事务开始的时间点上的数据库状态。这意味着在同一个事务中的所有查询的数据是根据一个单独的时间来保持一致性的,除了在下面的情况下例外:一个串行化所做的查询确实能够看到事务本身所做的变化。事务级别的读一致性可产生可重复读并且不会使一个查询暴露成为一个幻影。
在RAC中的读一致性
RAC使用为大家所知的缓存对缓存数据块传输的机制来从一个实例到另一个实例传输读一致的数据块镜象。RAC采用高速的低潜伏期的相互连接来满足数据块的远程需求。
Oracle隔离级别
Oracle提供下面这些事务隔离级别:
隔离级别 | 描述 |
提交后读 | 这个是默认的事务隔离级别。一个事务中所执行的每个查询只看见这个查询(而不是事务)开始之前的所提交的事务。一个oracle查询从来不读未提交(脏数据)的数据。 因为oracle 不会阻止其他事务来修改被一个查询正在读的数据块,所以数据可以在两个查询语句执行过程之间被其他事务所改变。因此,在一个有一个查询语句被执行两次的事务能够既经历不可重复读也产生一个幻觉。 |
串行化读 | 串行化读事务只看见在这个事务开始的时间点之前已经提交的数据变化,也能够看见事务本身通过insert,update,以及delete语句所做的变更情况。串行化读不会经历不可重复读或者幻觉。 |
只读 | 只读事务只看见在该事务开始的时间点之前的数据变化,并且不允许insert,update,以及 delete语句操作。 |
设置隔离级别
应用设计者,应用开发者以及数据管理员可以为不同的事务选择似的哪个的隔离级别,依赖于应用程序的工作量。你可以在事务开始的时候通过下面的语句来设置事务的隔离级别:
Set transaction isolation level read committed;
Set transaction isolation level serializable;
Set transaction read only;
使用Set transaction语句是为了节省启动每个事务的网络和处理成本,你可以使用alter session语句来为所有紧接着的事务设置事务隔离级别:
Alter session set isolation_level serializeable;
Alter session set isolation_level read commited;
提交后读隔离级别
Oracle默认的隔离级别是提交后读。隔离的程度是对那些有很小一部分的事务是有可能会冲突的环境下是适合的。Oracle根据它自己所属的物化视图时间点来印发每个查询,因此对于一个查询的多次执行允许不可重复读和幻觉,但是提供更高潜在的吞吐量。提交后读隔离级别在有少量事务可能冲突的隔离环境在是一个适合的级别。
串行化隔离级别
对于下面的环境串行化各级级别是适合的:
*只更新少量行的大数据库和短时间的事务;
*那些并发事务更新同一行的几率相对低的情况下;
*相对运行时间比较长的事务主要是只读的情况下;
如果事务之间已经被规划为一个接一个地执行,串行化读隔离级别允许并发地事务来做这些事务已经做的数据库的变更。特别地,只有当串行化事务开始时在oracle断定事务所做的对该行的前一次的变更已经提交的情况下,oracle才允许一个连续的事务来修改一个数据行。
为了使oracle的这个判定有效,oracle使用存储在数据块中的控制信息来说明在数据块中哪些行包含提交变更和未提交变更。在道理上,数据块包含数据块中影响每行的最近的事务的历史。保留的历史数量被create table和alter table语句中的initrans参数所控制。
在一些环境下,oracle没有足够的历史信息来判定一个数据行是否已经被最近的事务所更新。这个当许多事务并发地修改同一个数据块时这种情况能够发生。你可以通过为那些会有许多事务更新同一个数据块的表设置initrans参数为更高点的值来避免这个情况的发生。这样做来使oracle在每个数据块中分配足够的存储空间来记录访问该数据块的最近事务的历史记录。
当一个串行化事务试图更新或者删除被一个在该串行化事务开始之后已经提交的事务修改的数据时,Oracle产生一个错误:
ORA-08177 :对这个事务不能串行化
当一个串行化事务以出现“不能串行访问”的错误失败时,应用程序可以采取一个行为的任意一个方法:
*提交在那个时间点之前执行的工作;
*执行额外的(但是不同的)语句(可能在回滚到一个事务中的很早建立的回滚点的操作之后);
*撤消全部的事务;
图13-2显示了一个应用程序回滚并且在产生“不能串行访问”错误而失败时候重新尝试事务:
图13-2 串行事务失败
提交后读和串行化读隔离级别的比较
Oracle为应用程序开发者提供带有不同特征的两种事务隔离级别的选择。提交后读和串行化读级别都提供了一个高度的数据一致性和并发性。Oracle的读一致多版本并发控制模块以及排它的行级锁的执行为两种隔离级别提供减少资源竞争的好处,并且为真实情况下的应用调用所设计。
事务设置一致性
在oracle中为查看提交后读和串行化读隔离级别的一个有用的方法是需要考虑下面的想法:假设你已经有数据库表的集合,一个在这些表中行读的特定顺序,以及在任意特定时间点上提交的事务集合。如果所有事务的读返回的是被同一个提交的事务的集合所写的数据,这个操作(一个查询或者一个事务)是叫事务集合一致。如果一些事务读反映一个事务集合的变更以及其他事务读反映被其他事务所做的变更,这个操作不是事务集合一致。一个在效果上不是事务集合一致的操作看待数据库是处于一个反映的不是来自单独的提交事务集合的状态下。
Oracle在为每个语句都有事务集合一致性的情况下提供在提交后读模式下的事务执行。串行化模式为每个事务提供事务集合一致。
表13-2 总结了在oracle中提交后读和串行化读之间的关键的不同点:
表13-2 提交后读和串行化读的事务
| 提交后读 | 串行化读 |
脏数据写 | 不可能 | 不可能 |
脏数据读 | 不可能 | 不可能 |
不可重复读 | 可能 | 不可能 |
幻觉 | 可能 | 不可能 |
采用ANSI/ISO SQL 92 | 是 | 是 |
读真实的视图时间 | 语句 | 事务 |
事务集合一致 | 语句级 | 事务级 |
行级锁 | 是 | 是 |
读妨碍写 | 不是 | 不是 |
写妨碍读 | 不是 | 不是 |
不同行的写妨碍写 | 不是 | 不是 |
同一行的写妨碍写 | 是 | 是 |
等待妨碍的事务 | 是 | 是 |
受制于“不能串行化访问” | 不是 | 是 |
在妨碍的事务终止之后的错误 | 不会 | 不会 |
在妨碍的事务提交之后的错误 | 不会 | 会 |
行级锁
提交后读和串行化读事务都使用行级锁,并且如果它们试图去变更已经被一个未提交的并发事务更改的行的话那么提交后读和串行化读事务都将等待。试图去更新一个指定的行的第二个事务等待其他的事务来提交或者回滚事务并且释放该行的锁。如果那个其他的事务回滚,那个等待的事务不管该事务的隔离模式,可以继续去修改先前被锁的行好象其他事务已经不存在一样。
但是,如果其他阻塞事务提交并且释放它的锁,一个提交后读事务处理它想要修改的数据,但是对于串行化将会有“不能串行化访问”错误,因为在串行化事务开始之后其他事务已经提交了其所做的变更。
用作参考作用的完整性
因为在读一致或者串行化的事务中oracle不使用读锁,被一个事务所读的数据能够被其他事务所覆盖写。那些在应用程序级履行数据库一致性检查的事务不能假设这些事务所读的数据在事务执行阶段保持不变,尽管有些有改变没有被该事务所看见。数据库不一致会发生的除非这样的应用程序级的一致性检查是被编进代码时是牢记在心的,甚至在使用串行化事务。
分布式事务
在一个分布式环境下,在一个通过两个阶段提交事务的来确保所有的节点或者没有一个节掉提交的方式保护的多物理数据库中,一个指定的事务更新数据。在这样的一个环境下,所有参加一个串行化事务的服务器,无论是oracle的服务器还是非oracle的服务器,都被要求来支持串行化隔离模式。
如果一个串心化事务试图更新在一个不支持串行化事务的服务器所管理的数据库中的数据,那么该事务将接受到一个错误。只有当远程的服务器确实支持串行化事务时该事务才能够撤消并且重新尝试。
相反,提交后读事务能够在不支持串行化事务的服务器上执行分布式事务。
隔离级别的选择
和应用程序编码的需求一样,应用程序的设计者和开发者必须基于应用程序的性能和数据一致性来选择一个隔离级别。
对于具有多个并发用户快速提交事务的情况下,设计者必须在要求期待的事务的处理速度以及响应时间要求方面评定事务性能需求。经常地,对于高性能环境,对于隔离级别的选择是在数据一致性和并发性方面求的一个平衡。
检查数据库一致性的应用逻辑必须考虑一个事实就是在任意一个模式下读不能阻塞写。
通过行级锁和oracle的多版本并发控制系统的结合,oracle隔离模式提供了数据的高一致性,高并发性以及高性能。在oracle中读和写不会相互阻塞。因此,在多次查询能够看到一致性的数据的同时,提交后读和串行化读隔离为高性能都提供了高并发。并且不需要读未提交的数据。
提交后读隔离级别
对于许多应用程序来说,提交后读模式是一个最适合的隔离级别。提交后读隔离级别能够提供相当多的并发性,只带有一点点增长的不一致的数据结果的风险,这个归咎于对于一些事务的幻觉和不可重复读。
要求高事务到达率的许多高性能的环境要求比使用串行化隔离实现的环境有更多的吞吐量以及更快的响应时间。其他那些允许用户使用一个非常低的事务到达率的环境也允许由于幻觉并且不可重复读而产生不正确结果的低风险。提交后读隔离对于上面的两种环境是合适的。
Oracle提交后读隔离级别对于每个查询语句提供事务集合一致性。也就是,每个查询看见的数据是处于一致性的状态。因此,对于许多应用程序,如果它们运行在其他不使用多版本并发控制的数据库管理系统上,它们可能需要高度的隔离,提交后读的隔离级别将会满足它们的需求。
提交后隔离级别模式不需要应用程序逻辑来诱捕“不能串行化读”的错误并且循环回去来重新启动一个事务。在大多数的应用程序中,只有很少的程序有需要声明同样的查询两次的功能性需求,所以大多数应用程序对于幻觉和不可重复读的防止是不重要的。所以许多开发者选择提交后读隔离模式来避免写这样的错误检查和在每个事务中重新尝试代码的需要。
串行化隔离级别
Oracle的串行化隔离在那些两个并发的事务同时修改同一行的几率相对比较低的情况下和持续时间长的事务主要是只读的情况下是比较适合的。对于那些大的数据库并且持续时间比较短的只更新一些行记录的事务的情况下采用串行化隔离是最适合的。
串行化隔离模式对于防止幻觉和不可重复读提供稍微多点的数据一致性并且一个既允许读又允许写的事务执行查询超过一次的的情景下串行化提供这种保护是很重要的。
其他的隔离模式执行的时候,需要为读或者写锁住数据块,oracle为串行化隔离的执行提供无阻塞查询并且行级锁的精细粒度功能,oracle提供的这些功能减少了读/写的资源竞争。对于耗费主要是在读/写资源竞争的应用程序,oracle串行化隔离能够比其他系统提供明显地更多的吞吐量。因此,一些应用程序对于在oracle上而不是其他系统采用串行化隔离模式可能是比较适合的。
在一个oracle串行化事务中的所有查询看见在一个单独的时间点上的数据库,所以这个串行化隔离级别在有多个一致读在一个既读又写的事务中同时被发起的情形下使用是适合的。产生概要数据和将这些数据存储数据库中的一个报告写应用程序可能会用串行化隔离模式因为这种模式既能提供在只读事务所提供的读一致性,并且也允许插入,更新以及删除操作。
注意:带有子查询的DML语句的事务必须使用串行化隔离模式来保证读一致。
编写串行化事务需要应用程序开发者额外的工作来检验“不能串行化访问”的错误是否发生并且撤消和重新尝试执行这个事务。同样额外的编码工作也需要做用来在其他数据库管理系统中来管理死锁。为了坚持共同的标准或者为了运行在多数据库管理系统中的应用程序来说,设计串行化模式的事务可能是有必要的。那些检验串行化失败并且重新尝试的事务可以采用提交后读模式,因为这种模式不产生串行化错误。
串行化模式在一个相对持续时间长的事务更新的行同时也被大批短时间事务同时更新的情形下可能并不是最好的选择。因为一个运行时间更长的事务不可能是第一个来修改需要修改的行,它将重复地需要回滚,这样就浪费时间。注意一个传统的读锁,串行化模式悲观的执行将都不适合上面的情况,因为运行时间长的事务—尽管是读事务—会阻塞持续时间短的更新事务的进程并且反之亦然。
应用程序开发者当采用串行化模式时应该考虑回滚和重新尝试运行事务的成本。当使用读锁系统,并且在该系统中死锁的情况经常发生的环境下,串行化模式的使用需要通过终止事务的方法来回滚所做的工作并且重新来尝试运行原来的事务。在资源竞争激烈的环境下,这个活动可以占用重要的资源。
在大多数环境中,一个在接收到“不能串行化访问”的错误之后重新启动的事务是不可能和另一个事务发生冲突的。因为这个原因,所以在一个串行化事务中运行那些最有可能与其他事务竞争的语句运行的越早越有帮助。但是,这样并不能保证事务将会成功的完成,所以应用程序应该编写代码来限制尝试重新运行事务的数量。
尽管oracle串行化模式是和SQL92是兼容的并且与带有读锁的执行比较提供许多好处,但是串行化模式不提供与SQL92同样的语法。应用程序设计者必须考虑一个事实那就是在oracle中和其他系统一样读不会阻塞写。在应用程序级别检查数据库一致性的事务需要编码技术比如”select for update”的使用。这个问题需要被考虑在当采用串行化模式的应用程序是针对来自其他环境的oracle来说的时候。
静默数据库
你可以将系统处于静默状态。如果只存在是sys和system帐户下活动会话的话,系统就是处于静默状态。一个活动会话是指当前是在一个事务中,或者在一个查询,或者一个取数据过程中,获取是在PL/SQL存储过程中的会话,或者是当前持有任何共享资源的会话(例如:排队等候的队列是串行化共享数据库资源的内存结构,并且这种队列结构是与一个会话或者事务有联系的。)当数据库处于静默状态时,数据库管理员在此时是只能操作数据库的用户。
数据库管理员在数据库处于静默状态下能够执行在数据库处于非静默状态下不能安全处理的活动。这些活动包括:
*如果有并发的事务或者查询存在,一个可能在非静默状态下执行会失败的活动。例如:如果有并发事务访问同一张表,那么在非静默状态下想做改变一个数据库表对象的操作;
*那些执行过程中的中间影响可能对于并发的用户事务和查询来说是有害的的活动。例如:假设有张大表T,并且有对这张大表操作的一个PL/SQL存储包。你可以将T表分成两张表T1和T2,并且改变PL/SQL包使这个包对T表的引用可以用新的表T1和T2来代替。当数据库处于静默状态时,你可以做下面的操作:
Create table T1 as select … from T;
Create table T2 as select … from T;
Drop table T;
你可以删除旧的PL/SQL包然后再重新创建它。
对于必须是一直被操作的系统来说,不关闭数据库执行上面的操作的要求是危急而且是重要的。
当数据库处于静默状态时,数据库资源管理器阻塞所有非SYS和SYSTEM用户发起的所有活动。当数据库回归处于非静默状态时,所有非SYS和SYSTEM用户发起的活动被允许继续进行执行。用户也不会从处于静默状态下的数据库得到任何额外的信息。
一个数据库是如何别被设置为静默状态的
数据库管理员使用alter system quiesce restricted语句来使数据库处于静默状态。也只有SYS和SYSTEM用户能够发起ALTER SYSTEM QUIESCE RESTRICTED语句。对于处于打开状态下的所有的数据库实例,声明alter system quiesce restricted语句有下面的效果:
*oracle指导数据库所有的实例的数据库资源管理器来阻止所有不活动的会话(非SYS和SYSTEM用户)变为活动会话。对于非SYS和SYSTEM用户在此时是不能够启动一个新的事务,或者一个新的查询,取数据,或者也不能启动一个PL/SQL操作;
*oracle会等待在数据库的所有实例中所有存在被非SYS和SYSTEM用户发起的事务完成(或者被提交或者被终止)。Oracle也等待在所有数据库实例中非SYS和SYSTEM用户发起的以及不在事务中的正在运行的查询,取数据,以及PL/SQL存储过程的活动结束。如果一个查询被多个连续的OCI为取数据所运行,oracle不会等所有的取数据过程都完成。Oracle会等当前的取数据过程结束然后阻塞下一个取数据过程。Oracle也会等待所有的占用共享资源的非SYS和SYSTEM用户会话释放这些资源。在所有的操作结束之后,oracle使数据库设置为静默状态然后完成QUIESCE RESTRICTED语句的执行。
*如果一个实例正在运行在共享服务模式下,oracle会指导数据库资源管理器会阻塞非SYS和SYSTEM用户对那个数据库实例的登入。如果一个实例运行在非共享服务模式下,oracle不会强加任何用户对那个实例的登入限制。
在静默状态下,你不能在任何实例中改变资源管理计划。
ALTER SYSTEM UNQUIESCE语句将所有正在运行的实例还原到正常模式下,所以所有被阻塞的活动能够继续进行。数据库管理员可以通过查询$blocking_quiesce视图来确定是哪个会话正在阻塞一个静默状态转换工作的完成。
Oracle是如何锁数据的?
锁是在访问相同资源的事务之间阻止破坏性作用的机制,这些相同的资源或者是比如象表和行类似的用户对象或者是对用户是不可见的系统对象,不可见的系统对象比如:在内存中的共享内存结构和数据字典行。
在所有的案例中,当执行SQL语句时oracle自动获取必要的锁,所以用户不需要关心oracle是如何自动获取锁的详细信息。Oracle自动地使用约束的最低应用级别来提供最高程度的数据一致性也提供自动防故障装置的数据完整性。Oracle也允许用户手动地锁数据。
事务和数据一致性
Oracle通过在事务之间使用锁机制来保证数据一致性和完整性。因为oracle的锁机制是和事务控制有紧密关系的,所以应用设计者只要适当地定义好事务,然后oracle自动地管理锁。
牢记在心的是oracle锁是完全自动的并且不需要用户的任何活动。对于所有的SQL语句来说隐式锁是会发生的以便数据库用户从来不需要明确地去锁任何资源。Oracle默认的锁机制以约束的最低级别来锁数据来保证数据的完整性同时允许最高程度的数据并发。
锁的模式
Oracle在一个多用户数据库中使用两种模式的锁:
*显式的锁模式阻止相关的资源被共享。这种锁模式被获取用来修改数据的。第一个以相 外地方式锁一个资源的事务是仅有的一个事务能够修改资源信息直到该排外锁被释放。
*共享锁模式允许相关的资源能够被共享,主要依赖于所包含的操作。多用户读数据能够共享数据占用共享锁来阻止被一个写操作(写操作需要一个排外锁)并发的访问。几个事务可以得到对同一资源的共享锁。
锁持续时间
在一个事务中的语句所获取的所有的锁资源在事务持续过程中一直被占用,是为了阻止破坏性的干涉包括来自并发事务的脏读,不为人知的更新,以及破坏性的DDL操作。一个事务中的SQL语句所做的变更只有在第一个事务提交之后才能够被其他事务看见。
当你或者提交事务或者回滚事务时Oracle才释放这个事务中所获取的所有的锁资源。当事务回滚到保存点时Oracle也释放在保存点之后所获取的锁资源。但是,只有那些不需要等待先前的锁资源的事务能够获取现在可获取的资源的锁。正在等待的事务将会继续等待直到原始的事务完全提交或者完全回滚。
数据锁转换和锁升级
在一个事务中对所有被插入,被更新,被删除的行该事务会占用许多排外行锁。因为行锁是以约束的最高级别方式来获取的,锁转换是不需要或者是不执行的。
Oracle适当的时候会自动地将低约束级别的表锁转换为约束级别高的表锁。例如:假设一个事务使用带有for update语句的select查询语句来锁住该表的一些行。结果,这个SQL语句得到这个表的排外的行锁和一个行共享表锁。如果该事务之后更新被锁行的一行或多行,行共享表锁被自动地转换为一个行排外的表锁。
当许多锁是以粒度(例如:行)级别的方式被占用时锁升级会发生,并且一个数据库将这些锁升级到更高粒度级别的(例如:表)方式。例如:如果一个单独的用户锁住一张表的许多行,一些数据库会自动地将这个用户的行上的锁升级到一个单独的表锁。尽管锁的个数减少了,但是被锁的东西的约束增加了。
Oracle从来不升级锁。锁升级会极大地增加死锁的可能性。想象下面的情形:系统正在试图代表事务T1来升级锁,但是不能够升级锁是因为事务T2占用一些锁如果事务T2也需要在事务T1进行锁升级之前对同一数据进行锁升级,一个死锁将会发生。
死锁
当两个或者多个用户正在等待相互被锁的数据的时候那么一个死锁的情形会发生。死锁会阻止一些事务继续进行工作。图13-3是在一个死锁中两个事务假定的图解:
在图13-3中,在时间点A没有问题发生,每个事务拥有该事务试图更新的行上的行锁。每个事务可以不需要被终止地继续进行下去。但是,每个事务下一步试图更新当前被其他事务所占用的行。因此,在时间点B上死锁发生。因为两个事务都没有获取到他们各自需要的资源来继续下去或者终止掉。这样的话无论每个事务等待多长时间,这个冲突锁是一直存在的这样就是死锁。
图13-3 在死锁中的两个事务
死锁的探测
Oralce自动地检测死锁情形并且通过回滚包含在死锁语句中的一个语句的方式来解决死锁问题,因此这样释放了一个冲突的行锁集合。一个相应的信息也返回给遭受语句级别的回滚事务。被回滚的语句是属于一个监测到死锁的事务。通常,被通知的事务应该被显式地回滚掉,但是实际上被通知的事务是在等待之后重新尝试被要求回滚的事务。
注意;在分布式事务中,本地的死锁通过分析等待数据而被检测到,并且全局死锁是通过一个超时来被检测到的。一旦检测到死锁,非分布式和分布式死锁被数据库和应用程序以同样的方式处理这个问题。
死锁经常发生在当是事务显式指定锁并且覆盖掉oracle中的默认的锁动作的时候。因为oracle自身不会有锁升级也不会对查询使用读锁,但是确实会使用行级锁(而不是页级锁),在oracle中死锁不会经常发生。
避免死锁
多表的死锁如果访问同样的表的事务是按照表的顺序来锁表的话通常死锁可以避免,这个锁或者是隐式锁或者是显式锁。例如:所有的应用程序开发者可能会遵循这样的规则:当一个主表和一个明细表都被更新时,主表先被锁住然后再锁明细表。如果这样的规则很好的被设计并且在所有的应用程序中所遵循,死锁出现的几率是不会很高的。
当你知道你将为一个事务获取一连串的锁时,首先考虑得到最排外(即排外性最高级别)的锁。
锁的种类
Oracle自动地使用不同种类的锁来控制对数据的并发访问和阻止在用户之间的破坏性的作用。Oracle自动地代表事务来锁住一个资源来阻止其他事务对同一资源进行一些操作而需要的对这个资源进行排外的访问。当一些结果出现这个锁将被自动地释放因为该事务不再需要该资源。
在上面的操作整个过程中,oracle依据被锁资源和被执行的操作类型来自动地获取以不同约束级别模式的不同类型的锁。
Oracle锁属于下面三种一般类型的一种:
锁 | 描述 |
DML锁(数据锁) | DML锁是用来保护数据的。例如:表锁是锁住整张表,行锁是锁住被选的行。 |
DDL锁(字典锁) | DDL锁是用来保护用户对象的结构—例如:表和视图的定义。 |
内部的锁和闩 | 内部的锁和闩是用来保护数据库内部的结构比如:数据文件。内部的锁和闩是完全自动的被oracle操作的。 |
下面的部分讨论DML锁和DDL锁以及内部的锁。
DML锁
一个DML锁(数据锁)的目的是保证被多用户并发访问的数据的完整性。DML锁会阻止同时发生的相冲突的DML或者DDL操作的破坏性的干涉。DML语句自动地获取表级别的锁和行级别的锁。
注意:在每个锁类型或者锁模式以圆括号为首字母的缩写词是用在企业管理器中的锁监控里的缩写词。企业管理器可能 为任何表锁显示为TM锁,而不是显示表锁的模式(比如:RS或者SRX)。
行锁(TX锁)
行级锁是主要用来阻止两个事务同时修改同一行数据。当一个事务需要修改一行数据时,一个行锁将被获取。
被一个语句或者一个事务所占用的行锁的个数是没有限制的,并且oracle也没有将锁从行级别升级到更加粗造的粒度级别上。行级锁提供可能的最精粒度的锁并且也提供最好的数据并发性和吞吐量。
多版本的数据并发性和行级锁的结合意味着只有当同时访问相同的行的时候用户会竞争数据,特别是:
*数据的读操作用户不会等待相同行上的写操作用户;
*数据的写用户不会等待相同行上的读用户除非select ..for update语句被使用,select ..for update语句需要为读用户分配一个锁;
*写用户只有在其他写用户也在同一时刻试图更新同一行数据时会等待;
注意:数据的读用户可能在一些未决的分布式事务的非常特别的案例中只能等待相同数据行上写用户。
一个事务会为被下面所修改的每个单独的行获取一个行排外锁:insert,update,delete以及带有for update语句的select语句。
一个被修改的行总是被排外的锁住以便其他事务不能修改该行直到占用该锁的事务被提交或者被回滚掉。但是,如果事务结束是因为实例失败的话,在整个事务被恢复之前块级恢复会使得一行被获取。行级锁是当先前所列出的语句执行中oracle总是自动地分配的。
如果一个事务为一行获取一个行锁,事务也为相应的表获取一个表锁。这个表锁是为了阻止相冲突的DDL操作会覆盖在当前事务中的数据变化。
表锁(TM锁)
表级锁主要是用来在并发的DDL操作中做并发性控制,比如:在DML操作过程中阻止一张表被删除。当DDL或者DML语句是对一张表上操作,那么一个表锁将要被获取。表锁不会影响DML操作的并发性。对于分区表,表锁可以在是表级别或者是在子分区级别上的。
当一张表在下面的DML语句中被修改:insert,update,delete,带有for update语句的select语句,那么这个事务将会获取一个表锁。这些DML获取表锁是为了两个目的:为了代表一个事务保留DML操作对该表的访问以及为了阻止那些会与该事务相冲突的DDL操作。任何表锁是为了阻止对同一张表上的排外的DDL锁的获取并且阻止获取该锁的DDL操作。例如:如果一个未提交的事务为它自身占用一个表锁那么这张表就不能够被修改或者被删除。
一个表锁可以以下面几个模式中的任何一个模式被占用:行共享(RS),行排外(RX),共享(S),共享行排外(SRX)以及排外(X)模式。表锁模式的约束可以决定在对相同表上的其他能够获取到的和占用的表锁的模式。
表13-3显示了语句能够获取的表锁模式以及这些锁允许和禁止的操作:
表13-3表锁的总结
SQL语句 | 表锁模式 | 允许的表锁模式 | ||||
RS | RX | S | SRX | X | ||
Select …from table… | none | Y | Y | Y | Y | Y |
Insert into table… | RX | Y | Y | N | N | N |
Update table … | RX | Y* | Y* | N | N | N |
Delete from table … | RX | Y* | Y* | N | N | N |
Selete …from table for update of … | RS | Y* | Y* | Y* | Y* | N |
Lock table table in row share mode | RS | Y | Y | Y | Y | N |
Lock table table in row exclusive mode | RX | Y | Y | N | N | N |
Lock table table in share mode | S | Y | N | Y | N | N |
Lock table table in share row exclusive mode | SRX | Y | N | N | N | N |
Lock table table in exclusive mode | X | N | N | N | N | N |
RS: 行共享
RX: 行排外
S: 共享
SRX: 共享行排外
X: 排外
*Yes:如果没有相冲突的被其他事务所占用的行锁。否则,等待发生。
下面部分解释每个表锁模式,从最少约束到最多约束。它们也描述了引起事务获取每种模式的表锁的活动并且在那种锁模式下在其他事务中被一个锁所允许和禁止的活动。
行共享表锁(RS)
一个行共享表锁(有时候也被称为subshare table lock,SS)说明占有该表锁的事务已经锁住了该表的行并且意图更新它们。当下面的SQL语句被运行时一个行共享表锁对该表被自动地获取:
Select .. from table …for update of…;
Lock table talbe in row share mode;
一个行共享表锁是一个最少约束的表锁模式,提供对表最高的数据并发性。
允许的操作:被一个事务所占用的行共享表锁允许其他事务来查询,插入,更新,删除或者在同一张表中并发地锁行。因此,其他事务能够同时获取对该表的行共享,行排外,共享以及共享行排外锁。
禁止的操作:被一个事务所占有的一个行共享表锁阻止其他事务使用下面的语句对该表进行排外的写访问:
Lock table table in exclusive mode;
行排外表锁(RX)
一个行排外表锁(也被称为subexclusive table lock,SX)一般说明占有该锁的事务已经对该表中的行做了一个或者多个更新。对被下面的语句类型所修改的表,一个行排外表锁被自动地获取:
Insert into table …;
Update table …;
Delete from table …;
Lock table table in row exclusive mode;
一个行排外表锁比一个行共享表锁有稍微多点的约束。
允许的操作:被一个事务所占有的一个行排外表锁允许其他事务查询,更新,删除或者在同一张表中并发地锁行。因此,行排外锁允许多事务同时获取对相同表的行排外锁和行共享锁。
禁止的操作;被一个事务所占有的行排外锁阻止其他事务为排外的读或者写来手动锁表。因此,其他事务不能够并发地使用下面的语句锁表:
Lock table table in share mode;
Lock talbe table in share exclusive mode;
Lock table table in exclusive mode;
共享锁(S)
当指定下面的语句时一个共享锁将被自动地获取:
Lock table table in share mode;
允许的操作:被一个事务所占有的一个共享锁允许其他是事务只能查询该表,使用select ..for update语句或者成功地运行lock table…in share mode语句是可以用来锁特定的行。其他事务中的更新操作是不允许的。多事务可以并发地占用同一表的共享表锁。在这个案例中,没有事务能够更新表(即使使用带有for update语句的select查询语句使这个事务占用行锁)。因此,一个占有一个共享表锁的事务只有在没有其他事务也拥有对同一张表的一个共享表锁时才能够更新表。
禁止的操作:被一个事务所占有的一个共享表锁阻止其他事务修改同一张表并且也阻止执行下面的语句:
Lock table table in share row exclusive mode;
Lock table table in exclusive mode;
Lock table table in row exclusive mode;
共享排外表锁(SRX)
一个共享排外表锁(也称谓一个share-subexclusive table lock,SSX) 比一个共享表锁有更多的约束。可以通过下面的语句来获取一张表的一个共享行排外表锁:
Lock table table in share row exclusive mode;
允许的操作:在一个被给予的表上一次只能有一个事务获取一个共享行排外表锁。被一个事务所占有的一个共享行排外表锁允许其他事务查询或者使用带有for update语句的select查询语句来锁定特定的行,但是不允许更新表。
禁止的操作:被一个事务所占有的一个共享行排外表锁阻止其他事务获取行排外锁并且阻止修改同一张表的信息。一个共享行排外表锁也禁止其他的事务获取共享表锁,共享行排外表锁以及排外表锁,这些阻止了其他事务执行下面的语句:
Lock table table in share mode;
Lock table table in shre row exclusive mode;
Lock table table in row exclusive mode;
排外锁(X)
一个排外表锁是表锁中约束最多的一个锁模式,允许拥有这个锁的事务对表有写操作。一个排外表锁通过下面的语句可以获取:
Lock table table in exclusive mode;
允许的操作:被一个事务所占有的一个排外表锁禁止其他事务主席任何类型的DML语句或者禁止在表上放置任何类型的锁。
为DML语句自动地所获取的DML锁
前一部分解释了不同种类的数据锁,事务能够占有的锁模式,什么时候能够被获取这些锁,以及什么操作是禁止的。这些的部分将总结代表不同的DML操作oracle是如何自动地锁数据的?
表13-4总结了下面部分的信息:
表13-4 DML操作所获取的锁
DML语句 | 行锁 | 表锁模式 |
Select …from table |
|
|
Insert into table … | X | RX |
Update table … | X | RX |
Delete from table … | X | RX |
Select …from table … for update of … | X | RS |
Lock table table in … |
|
|
Row share mode |
| RS |
Row exclusive mode |
| RX |
Share mode |
| S |
Share exclusive mode |
| SRX |
Exclusive mode |
| X |
X:排外
RX:行排外
RS:行共享
S:共享
SRX:共享行排外