深究Oracle的隔离级别(原创)
多用户环境下的数据并发访问及数据一致性简介
在只有单一用户的数据库中,用户可以任意修改数据,而无需考虑同时有其他用户正在修改相同的数据。但在一个多用户数据库中,多个并发事务中包含的语句可能 会修改相同的数据。数据库中并发执行的事务最终应产生有意义且具备一致性的结果。因此在多用户数据库中,对数据并发访问(data concurrency)及数据一致性(data consistency)进行控制是两项极为重要的工作。
1、数据并发访问指多用户同时访问相同的数据。
2、数据一致性指系统中每个用户都能够取得具备一致性的数据,同时还能够看到自己或其他用户所提交的事务对数据的修改。
为了描述同时执行的多个事务如何实现数据一致性,数据库研究人员定义了被称为串行化处理(serializability)的事务隔离模型(transaction isolation model)。当所有事务都采取串行化的模式执行时,我们可以认为同一时间只有一个事务在运行(串行的),而非并发的。
以串行化模式对事务进行隔离的效果很好,但在此种模式下应用程序的效率将大大降低。将并行执行的事务完全隔离意味着即便当前只存在一个对表进行查询(query)的事务,其他事务也不能再对此表进行插入(insert)操作了。总之,为了满足实际要求,我们需要在事务的隔离程度与应用的性能之间找出一个平衡点。
Oracle 支持两种事务隔离级别(isolation level),使应用程序开发者在对事务进行控制时,既能保证数据的一致性,又能获得良好的性能。
需要防止的现象和事务隔离级别
ANSI/ISO SQL 标准(SQL92)定义了四种事务隔离级别(transaction isolation level),这四种隔离级别所能提供的事务处理能力各不相同。这些事务隔离级别是针对三种现象定义的,在并发事务执行时,需要阻止这三种现象 中的一种或多种发生。
三种需要阻止的现象(preventable phenomena)是:
1、脏读取(dirty read):一个事务读取了被其他事务写入但还未提交的数据。
2、不可重复读取(nonrepeatable read):一个事务再次读取其之前曾经读取过的数据时,发现数据已被其他已提交的事务修改或删除。
3、不存在读取(phantom read):事务按照之前的条件重新查询时,返回的结果集中包含其他已提交事务插入的满足条件的新数据。
SQL92 标准中定义了四个隔离级别,在各隔离级别中,允许发生上述三种需要阻止的现象中的一种或多种。详情见下表
现象 | 脏读取 | 不可重复读取 | 不存在读取 |
隔离级别 | |||
未提交读取(read uncommitted) | 允许 | 允许 | 允许 |
已提交读取(read committed) | 不允许 | 允许 | 允许 |
可重复读取(repeatable read) | 不允许 | 不允许 | 允许 |
串行化(rerializable) | 不允许 | 不允许 | 不允许 |
Oracle 支持三种事务隔离级别:已提交读取,串行化,以及 SQL92 中没有包含的只读模式(read-only mode)。已提交读取是 Oracle 默认使用的事务隔离级别。
Oracle 如何管理数据并发访问及数据一致性
Oracle 利用多版本一致性模型(multiversion consistency model),各种类型的锁及事务来管理多用户系统中的数据一致性(data consistency)。
多版本并发访问控制
Oracle 能够自动地实现一个查询的读一致性,即一个查询所获得的数据来自同一时间点(single point in time)(这也被称为语句级读一致性(statement-level read consistency))。Oracle 还能令一个事务内的所有查询都具备读一致性(即事务级读一致性(transaction-level read consistency))。
Oracle 利用回滚段中的信息生成一个能保证一致性的数据视图。回滚段内保存了未提交或最近提交的事务中所修改数据的原值。下图展示了 Oracle 如何利用回滚段实现语句级的读一致性。
在查询开始执行时,将记录当前的系统变化编号(system change number,SCN)。在 图 中,记录的系统变化编号为 10023。当查询进行扫描时,只会使用有效的(observed)数据块。如果某个数据块内的数据被修改过(即数据块的 SCN 晚于查询开始执行时记录的 SCN),Oracle 将使用回滚段中的信息重建此数据块,并以重建的数据块替代被修改的数据块供查询使用。因此,查询的结果集只包含查询开始执行时就已经提交的数据。在查询执行时,其他事务修改的数据对此查询来说是无效的,这保证了每个查询都能得到满足一致性的数据。
语句级读一致性
Oracle 强制实现语句级读一致性(statement-level read consistency)。这保证了单一查询的结果集来自一个时间点——即查询开始执行的时间。因此,一个查询的结果集永远不会包含脏数据及此查询执行时 其他事务提交的数据。在一个查询执行期间,只有在查询执行前提交的数据对此查询才是可见的。查询无法看到其开始执行后提交的数据。
任何一个查询都能得到满足一致性的结果集,这保证了用户无需额外操作就能确保数据一致性。 SELECT ,使用子查询的 INSERT ,及包含显式或隐式查询的 UPDATE 或 DELETE 语句,都能够保证数据一致性。上述语句通过一个查询(query)来得到她们所需的满足一致性的结果集(分别使用 SELECT,INSERT,UPDATE 或 DELETE 语句)。
SELECT 语句是一个显式地查询,且其中可以包含嵌套查询(nested query)或连接操作(join operation)。 INSERT 语句中也能够使用嵌套查询。 UPDATE 及 DELETE 语句能够利用 WHERE 子句或子查询进行限制,只操作数据表内的部分数据行。
INSERT , UPDATE ,及 DELETE 语句中包含的查询能够获得一致性的结果集。这些查询无法看到其所在 DML 语句对数据的修改。换句话说,这些查询只能看到其所在 DML 语句开始之前的数据。
TIPS:如果 SELECT 列表中存在 PL/SQL 函数,那么函数中包含的 SQL 语句将遵从其自身的语句级读一致性,而非其所在 SQL 的读一致性。例如, SELECT 语句中的某个函数访问的表可能会在语句执行时被其他事务修改并提交。此函数每次执行时都将建立一个新的一致性视图(snapshot)。
事务一致性读
Oracle 还能够实现事务级读一致性(transaction-level read consistency)。当一个事务运行在串行化模式(serializable mode)下时,则事务内所有数据访问均反映的是事务开始时的数据状态。即事务内的所有查询对某个时间点来说具备一致性,但是运行在串行化模式下的事务能够看到事务自身对数据所作的修改。事务级的读一致性能够保证可重复读取并可阻止出现不存在读取。
RAC环境下的读一致性
RAC 系统采用缓存对缓存(cache-to-cache)的数据块传输机制(此技术被称为 Cache Fusion)在实例间传输满足读一致性(read-consistent)的数据块镜像。RAC 系统通过高速度低延迟的内部连接(interconnect)实现上述数据传输,从而满足实例之间对数据块的请求。
Oracle 事务隔离级别
Oracle 支持以下三种事务隔离级别(transaction isolation level)。
隔离级别 | 描述 |
已提交读取 | Oracle 默认使用的事务隔离级别。事务内执行的查询只能看到查询执行前(而非事务开始前)就已经提交的数据。Oracle 的查询永远不会读取脏数据(未提交的数据)。 Oracle 不会阻止一个事务修改另一事务中的查询正在访问的数据,因此在一个事务内的两个查询的执行间歇期间,数据有可能被其他事务修改。举例来说,如果一个事务内同一查询执行两次,可能会遇到不可重复读取或不存在读取的现象。 |
串行化 | 串行化隔离的事务只能看到事务执行前就已经提交的数据,以及事务内 INSERT , UPDATE ,及 DELETE 语句对数据的修改。串行化隔离的事务不会出现不可重复读取或不存在读取的现象。 |
只读模式 | 只读事务只能看到事务执行前就已经提交的数据,且事务中不能执行 INSERT , UPDATE ,及 DELETE 语句。 |
应用程序的设计开发者及数据库管理员可以依据应用程序的需求及系统负载(workload)而为不同的事务选择不同的隔离级别(isolation level)。用户可以在事务开始时使用以下语句设定事务的隔离级别:
已提交读模式:SET TRANSACTION ISOLATION LEVEL=READ COMMITTED;
串行模式:SET TRANSACTION ISOLATION LEVEL= SERIALIZABLE;
只读模式:SET TRANSACTION= READ ONLY;
(经笔者实验以上命令在非sysdba用户下可以成功执行,sysdba下不能执行,待验证正确性)
如果在每个事务开始时都使用 SET TRANSACTION 语句,将加重网络及处理器的负担。用户可以使用 ALTER SESSION 语句 改变一个会话所有内事务的默认隔离级别:
1、ALTER SESSION SET ISOLATION_LEVEL SERIALIZABLE;
2、ALTER SESSION SET ISOLATION_LEVEL READ COMMITTED;
(无法设置会话级别的只读隔离模式)
已提交读取隔离
Oracle 默认使用的隔离级别(isolation level)是已提交读取(read committed)隔离。这种程度的隔离适合在事务发生冲突的可能性较小的系统中使用。在这种隔离级别下,Oracle 能够保证事务内每个查询在执行期间都拥有一个唯一的数据视图,因此事务可能出现不可重复读取或不存在读取的现象,但此时系统的数据处理能力较高。
串行化隔离
符合以下特性的系统适合采用串行化隔离(serializable isolation):
1、数据量大,但事务短小,只会更新较少数据行的数据库
2、两个并发事务修改相同数据的概率较小
3、运行时间相对较长的事务只执行只读操作
在串行化隔离下,并发事务对数据库进行修改时只能顺序执行。具体来说,在串行化隔离下,Oracle 在允许一个采用串行化隔离的事务修改某些数据行时,需要判断在此事务开始执行之前,其他所有事务对这些数据行的修改已经被提交。
为了实现上述判断,Oracle 在数据块内存储了相关的控制信息,用于记录此块内数据行中所包含的数据是已提交或未提交的。即数据块内记录了近期对本数据块内数据行进行了修改的所有事务及事务的状态。在一个数据块内能够保留多少这样的记录是由 CREATE TABLE 或 ALTER TABLE 语句中的 INITRANS 参数设定的。
有些情况下,Oracle 无法获得足够的历史信息来判断某个数据行是否被一个事务修改过。当大量事务在短时间内并发地修改同一数据块就会出现以上情况。用户可以为可能被多个事务同时更新相同数据块的表设置较大的 INITRANS 值,以便避免上述情况。设置了较大的 INITRANS 值后,Oracle 就能为每个数据块分配足够的空间来记录访问此数据块的事务的信息。
当一个串行化事务试图更新或删除数据,而这些数据在此事务开始后被其他事务修改并进行了提交,Oracle 将报错:
ORA-08177: 无法进行串行化访问
当一个串行化事务因为 无法进行串行化访问 (Cannot serialize access)错误而失败时,应用程序可以 选择以下几种处理方式:
1、将错误发生之前的操作提交
2、执行其他操作(执行前可以回滚到事务内的某个保存点)
3、撤销整个事务
下图显示了一个事务遇到 无法进行串行化访问 后, 程序进行回滚并尝试重新执行此事务的例子:
图中显示了一个串行化事务,其中首先执行了两个相同的 SELECT 语句,接着执行了一个 UPDATE 语句。即便在两个 SELECT 执行之间有其他事务修改了相关数据,这两个 SELECT 也能够返回相同的结果。当 UPDATE 语句更新数据时,所更新的数据在此事务开始后被其他事务修改并提交过,Oracle 将报错 Cannot Serialize Access。这个错误将导致事务回滚并尝试重新执行。
已提交读取隔离与串行化隔离的区别
Oracle 为应用程序开发者提供了两种特性相异的事务隔离级别。已提交读取隔离和串行化隔离都能实现高度的数据一致性及并发访问能力。这两种隔离级别都能够利用 Oracle 的读一致性多版本并发访问控制模型及独有的行级锁(row-level locking)技术,从而减少并发事务间的竞争。应用程序开发者可以使用这两种隔离级别开发符合现实要求的应用系统。
事务集数据一致性
我们可以参考以下场景来研究 Oracle 中的两种隔离级别:假设现有一组数据库表(或称为一组数据集),一系列读取表数据的查询,以及一组在任意时间提交的事务。如果一个数据库操作(一个查询或一个事务)中所有读取返回的数据是由同一组已提交事务写入的,我们就称此操作满足事务集数据一致性(transaction set consistent)。相反,当一个数据库操作内的不同读取反映了不同事务集对数据的修改,此操作就不满足事务集数据一致性。换句话说,一个不满足事务集数据一致性的操作所看到的数据库的状态是由不同的已提交事务集决定的。
在已提交读取隔离模式下,Oracle 能保证每个语句的事务集数据一致性。而在串行化隔离模式下,Oracle 能保证每个事务的事务集数据一致性。
下表总结了 Oracle 中已提交读取事务和串行化事务的关键区别。
已提交读取 | 串行化 | |
脏写入(dirty write) | 不可能 | 不可能 |
脏读取(dirty read) | 不可能 | 不可能 |
不可重复读取(nonrepeatable read) | 可能 | 不可能 |
不存在读取(phantom) | 可能 | 不可能 |
与 ANSI/ISO SQL 92 标准兼容 | 是 | 是 |
唯一的数据视图的使用范围 | 语句 | 事务 |
事务集数据一致性 | 语句级 | 事务级 |
行级锁 | 是 | 是 |
读操作(reader)阻塞写操作(writer) | 否 | 否 |
写操作阻塞读操作 | 否 | 否 |
针对不同数据行的写操作 是否相互阻塞 | 否 | 否 |
针对相同数据行的写操作 是否相互阻塞 | 是 | 是 |
等待导致阻塞的事务(blocking transaction) | 是 | 是 |
会出现 无法进行串行化访问 (Cannot serialize access)错误 | 否 | 是 |
在导致阻塞的事务结束后 将发生错误 | 否 | 否 |
在导致阻塞的事务提交后 将发生错误 | 否 | 是 |
选择隔离级别
已提交读取隔离
对于大多数应用来说,已提交读取隔离是最适合的事务隔离级别。已提交读取隔离能够最大限度地保证数据并发性,但在某些事务中可能会出现不可重复读取或不存在读取,因此略微增加了出现数据不一致性的风险。
在对性能要求较高的系统中,为了应对较高的事务到来率(transaction arrival rate),系统需要提供更大的事务吞吐量和更快的响应速度,此时采用串行化隔离可能难以实现。还有一类系统,其事务到来率较低,出现不可重复读取或不存在读取的风险也较低。以上两种系统均适合采用已提交读取隔离 。
Oracle 的已提交读取隔离能够确保所有查询的事务集数据一致性。即查询获得的数据是处于一致性状态下的。因此在 Oracle 中已提交读取隔离能够满足大多数应用的要求。而在没有多版本并发访问控制的数据库管理系统中,开发者可能需要采用更高程度的隔离方式。
在已提交读取隔离模式下,开发者不需要在应用逻辑中捕获 无法进行串行化访问 错误,也无需回滚并重新执行事务。在大多数应用程序中,几乎不会有在一个事务中执行同一查询多次的情况,因此在这些应用程序中,为防止出现不可重复读取或不存在读取而采取的保护措施并不重要。 如果开发者选择已提交读取隔离,就能够省略在每个事务中加入错误检查及事务重做的代码。
Oracle 的串行化隔离适合于具备以下特点的系统:出现修改相同数据的事务的几率较小,且长时间执行的事务以只读操作为主。最适合采用串行化隔离的系统是大型数据库,且其中主要运行更新少量数据的短小事务。
串行化隔离能够提供更好的数据一致性,她能阻止不可重复读取或不存在读取的现象。当一个读或写事务中需要运行同一查询多次时,串行化隔离的作用更加明显。
某些数据库管理系统在实现串行化隔离时,无论读写操作都要对整个数据块加锁。而 Oracle 则采用了无阻塞查询及低粒度的行级锁技术,减少了读写操作间的竞争。对于存在较多读写竞争的应用,Oracle 的串行化隔离与其他数据库管理系统相比能够大大地提高事务处理能力。因此,某些应用在 Oracle 中可以采用串行化隔离,而在其他数据库管理系统则未必可行。
运行在串行化隔离模式下的事务中的所有查询所获得的数据都来自同一时间点,因此这种隔离级别适合于需要执行多个满足一致性的查询的事务。例如,汇总数据并将结果写入数据库的报表应用可以采用串行化隔离,因为串行化事务所提供的数据一致性与 READ ONLY 事务相同,但其中还可以执行 INSERT , UPDATE ,和 DELETE 操作。
参考至:Oracle 10G R2 Concepts
如有错误,欢迎指正