数据并发和一致性介绍
在单用户的数据库中,用户可以修改数据,而不用担心其他用户在同一时间修改相同的数据。但是,在一个多用户的数据库中,多个事务内的语句可以同时更新相同的数据。同时执行的多个事务必须产生有意义且一致的结果。
因此,多用户数据库必须提供以下功能:
1、数据并发性,确保多个用户可以同时访问数据
2、数据一致性,确保每个用户看到数据的一致的视图,包括可以看到用户自己的事务所做的更改,和其他用户已提交的事务所做的更改。
为描述当多个事务同时运行时的事务一致性行为,数据库研究人员定义了一种称为可串行性的事务隔离模型。可串行化事务在一种使其看起来好像没有其他用户正在修改数据库中的数据的环境中运作。
虽然事务之间的这种隔离度好像不错,但在可序列化模式下运行许多应用程序可能会严重影响应用程序吞吐量。对并发运行事务的完全隔离可能意味着一个事务无法在某个正在被另一个事务查询的表上执行插入操作。简而言之,现实的考虑通常需要在完美的事务隔离性和性能之间的一个折衷。
Oracle数据库针对数据并发和一致的解决方案
Oracle 数据库通过使用多版本一致性模型和各种类型的锁和事务,来维护数据的一致性。通过这种方式,数据库可以向多个并发用户呈现一个数据的视图,每个视图都在某个时间点上是一致的。因为不同版本的数据块可以同时存在,事务可以读取在某个查询请求时间点上的已提交版本数据,并返回符合一个单一时间点的结果。
多版本读一致性
在 Oracle 数据库中,多版本即同时实现数据的多个版本的能力。
Oracle 数据库维护多版本读取一致性,这意味着数据库查询具有以下特征:
读一致查询
查询所返回的数据已提交的,且关于某个单一时间点一致。
备注:Oracle 数据库绝不允许脏读。当一个事务读取了另一个事务中未提交的数据时,会发生脏读。为说明脏读的问题,假设一个事务更新某列的值,但不提交。第二个事务读取此已更新的脏 (未提交) 值。第一个会话回滚了事务,使该列仍具有其旧值,但第二个事务继续使用更新的值,这会损坏数据库。脏读会破坏数据的完整性、 破坏外键、和忽略唯一约束。
非阻塞查询
数据的读取者和写入者不会相互阻塞。
语句级读取一致性
Oracle 数据库始终强制执行
语句级读取一致性,保证单个查询所返回的数据是已提交的、且关于某个单一时间点一致
。单个 SQL 语句所一致的时间点取决于事务的隔离级别和查询的性质:
1、在读提交隔离级别,该时间点是语句打开的时间。例如,如果一个SELECT 语句在 SCN 1000 时打开,则此语句一致于 SCN 1000。
2、在可串行化或只读事务隔离级别,该时间点为事务开始的时间。例如,如果一个事务开始于 SCN 1000,且在该事务中有多个 SELECT 语句发生,则每个语句都一致于 SCN 1000。
3、在闪回查询操作(SELECT … AS OF)中,SELECT 语句显式指定时间点。例如,你可以查询某个表在上星期四下午 2 时的数据
事务级读取一致性
Oracle 数据库还可以为一个事务中的所有查询提供读取一致性,这称为事务级读取一致性。在这种情况下,事务中的每个语句都看到来自同一时间点(即该事务开始的时间)的数据。
在一个可序列化事务中的多个查询,能看到事务本身所做的更改。例如,某个事务更新了 employees 表,然后其后续查询将看到对 employees 所做的更新。事务级读取一致性产生可重复的读取,且不会产生幻读读。
读取一致性及撤消
为管理多版本的读取一致性模型,当表同时被查询和更新时,数据库必须创建一组读取一致的数据。Oracle 数据库通过使用撤销数据实现了这一目标。
每当用户修改了数据,Oracle 数据库会创建撤销条目,并写入到撤销段 。
撤销段包含由未提交事务或最近提交的事务所更改的数据的旧值。因此,同一数据在各个不同时间点上的多个版本,都可以存在于数据库中。数据库可以使用在不同时间点的数据快照,来提供数据读取一致视图,并实现非阻塞查询。
读取一致性在单实例和 Oracle 真正应用集群 (Oracle RAC) 环境中都可以得到保证。Oracle RAC 使用一种称为缓存融合的“缓存到缓存”的数据块传输机制,将一个数据库实例中的数据块读取一致映像传送到另一个实例中。
读一致性:示例
下图显示了一个查询,在已提交读隔离级别使用撤销数据以提供语句级的读取一致性。
当数据库为某个查询检索数据块时,数据库确保每个块中的数据反映了该查询开始时的内容。
数据库根据需要回滚对数据块所做的更改,以将块重建到查询处理开始的状态。
数据库使用一种称为 SCN 的机制,来保证事务的顺序。当 SELECT 语句进入执行阶段时,数据库会确定查询开始执行时所记录的 SCN。在上图中,该 SCN 为 10023。在事务中的每个查询必须返回在 SCN 10023 时的已提交数据。
在上图中,其 SCN 大于 10023 的块具有已更改数据,如图中的两个具有SCN 10024 的块所示。SELECT 语句需要一个与已提交更改块一致的版本。该数据库将当前数据块复制到新的缓冲区,并应用撤消数据,以重新构造块的早期版本。这些重建的数据块被称为一致读取 (CR) 克隆。
在上图中,数据库创建了两个 CR 克隆: 一个块与 SCN 10006 一致,而另一个块与 SCN 10021 一致。数据库为查询返回重建的数据。通过这种方式,数据库可以防止脏读。
读取一致性和事务表
1、数据库使用一个称为感兴趣事务列表 (
interested
transaction list
ITL) 的事务表,来确定当数据库开始修改块时是否某个事务还未提交。
2、每个段块的块头包含一个事务表。
3、事务表中的条目描述了哪些事务有被锁定的行,以及块中的哪些行包含提交和未提交的更改。
4、事务表指向撤销段,提供对数据库所做的更改的时间相关信息。
5、在某种意义上,块头包含影响块中每个行的事务的最近历史记录。CREATETABLE 和 ALTER TABLE 语句的 INITRANS 参数,控制被保留的交易历史记录条数。
锁定机制
通常,多用户数据库使用某种形式的数据锁定,来解决与数据并发性、一致性、和完整性相关的问题。
锁是防止访问同一资源的事务之间的破坏性相互作用的机制。
ANSI/ISO 事务隔离级别
由 ANSI 和 ISO/IEC 采纳的 SQL 标准,定义了四个事务隔离级别。这些级别对事务处理吞吐量有不同程度的影响。
这些隔离级别根据在同时运行的事务之间必须防止的现象来定义。可预防的现象有:
脏读
一个事务读取了已被另一个事务写入、但尚未提交的数据。
不可重复读
一个事务重新读取之前曾经读取过的数据,发现另一个已提交的事务已修改或删除了该数据。例如,用户查询某行,然后稍后又查询相同的行,却发现数据已更改。
幻像读
一个事务重新运行满足某搜索条件的查询,并返回一个行集,发现另一个已提交的事务已插入了满足搜索条件的其他行。
例如,一个事务查询雇员数目。五分钟后它执行相同的查询,但现在人数却增加了一个,这是因为另一个用户为一名新员工插入了一条记录。满足查询条件的数据比之前更多了,但与不可重复读不同,之前读取的数据不会变化。
根据运行在某个特定的隔离级别的事务所允许发生的现象,SQL 标准定义了四个隔离级别。下面显示了这些级别。
隔离级别 脏读 不可重复读 幻像读
未提交读 可能 可能 可能
已提交读 不可能 可能 可能
可重复读 不可能 不可能 可能
可串行化 不可能 不可能 不可能
Oracle 数据库提供了
已提交读 (默认值)
和可串行化隔离级别。另外,数据库也可以提供一种只读模式。
Oracle 数据库事务隔离级别概述
上面总结了事务隔离级别的 ANSI 标准。该标准定义了在各个隔离级别所允许或必须防止的现象。
Oracle 数据库提供如下事务隔离级别:
1、已提交读隔离级别
2、可串行化隔离级别
3、只读隔离级别
读提交隔离级别
在(默认的)已提交读隔离级别中,事务中执行的每个查询,仅看到在查询开始之前提交的数据 ——而不是事务开始之前提交的数据。这一隔离级别适合于几乎不可能发生事务冲突的数据库环境。
已提交读事务中的查询可以避免读取在查询过程中所提交的数据。例如,如果一个查询正扫描到一个百万行表的中间,而另一个不同的事务对第950000 行提交了一个更新,但当查询读到第 950000 行时,它并不能看见这个变化。因为数据库不会阻止其它事务修改一个查询所读取的数据,其他事务可能会在查询执行期间更改数据。因此,两次运行相同查询的事务可能会遇到模糊读取和幻像读取现象。
在已提交读隔离级别中的读取一致性
为每个查询提供一个一致的结果集,其目的是为了保证数据一致性,而无需用户采取任何行动。对于隐含的查询(如在一个 UPDATE 语句中的 WHERE 子句),也同样可以保证其一致的结果集。但是,在隐式查询中的每个语句不会看到 DML 语句本身所做的更改,只能看到更改之前所存在的数据。
如果 SELECT 列表中包含一个 PL/SQL 函数,则数据库在该 PL/SQL 函数代码内运行的 SQL 所在语句级别(而不是在父 SQL 级别)上,应用语句级别读取一致性。例如,一个函数可能会访问某个表,其数据被另一个用户更改并提交。在 SELECT 语句中的每次函数运行,都会建立一个新的读一致性快照。
在读提交事务中的写入冲突
在一个读已提交事务中,当事务尝试更改由另一个未提交并发事务(有时称为阻塞事务)所更新的行时,会发生写入冲突。读提交事务将等待阻塞事务结束并释放其行锁。有两个选项如下所示:
1、如果阻塞事务回滚,正在等待的事务将继续并更改之前被锁定的行,就像另一个事务从未存在一样。
2、如果阻塞事务提交并释放了锁,则正在等待的事务将对这个刚被更新的行继续其预定更新。
可串行化隔离级别
在可串行化隔离级别,事务只看到自事务开始以来(而不是自查询以来)该事务本身所提交的更改。可串行化事务的运行环境,使其看起来好像没有其他用户在修改数据库中的数据。
可串行化隔离适合如下环境:
1、大型数据库中只更新少数几行的短事务
2、两个并发事务将修改相同的行的可能性相对较低
3、较长时间运行的事务主要为只读事务
在可串行化隔离级别,在语句级别所获得的读取一致性通常延伸到整个事务范围。当重新读取在同一事务中之前读取的任何行时,保证结果相同。可以保证任何查询在该事务的持续期间返回相同的结果,因此其他事务所做的更改是不可见的,无论该查询已运行了多长时间。可串行化事务不会遇到脏读、 模糊读取、或幻读。
Oracle 数据库允许可串行化事务修改行,只要当可序列化事务开始时,由其它事务对行所做更改已提交。
当一个串行化事务试图更新或删除某数据,而该数据在串行化事务开始后被一个不同的事务更改并提交,则数据库将生成一个错误:
ORA-08177: Cannot serialize access for this transaction
当可序列化事务失败,产生 ORA-08177 错误时,应用程序可以采取行动,包括以下几种:
1、将所执行的工作提交到该点
2、也许要先回滚到事务中之前建立的某保存点,然后执行一些其他额外 的(不同)语句
3、回滚整个事务
如果可串行化事务不会尝试更改由另一个事务在该可序列化事务开始后所提交的行,就可以避免串行化访问问题。
只读隔离级别
只读隔离级别类似于可串行化隔离级别,但只读事务不允许数据在事务中被修改,除非该用户是 SYS。因此,只读事务不会受到 ORA-08177 错误的影响。只读事务可用于生成报表,其内容必须与事务开始时保持一致。
Oracle 数据库通过按需从撤销段重建数据,来实现读取一致性。因为撤消段是以一个循环方式使用的,数据库可以覆盖撤销数据。长时间运行的报表可能有一定的风险,读取一致性所需要的撤销数据,可能已被一个不同的事务重用,并抛出快照太旧(snapshot too old)错误。设置一个撤消保留期,即在旧数据被覆盖之前,数据库尝试保留撤消数据的最短时间,以避免这一问题。