Oracle多版本机制

这个主题与并发控制的关系非常紧密,因为这正是Oracle并发控制机制的基础,Oracle采用了一种多版本、读一致(read-consistent的并发模型。再次说明,我们将在第7章更详细地介绍有关的技术。不过,实质上讲,Oracle利用这种机制提供了以下特性:

 

 

 

 

q  读一致查询:对于一个时间点(point in time),查询会产生一致的结果。

q  非阻塞查询:查询不会被写入器阻塞,但在其他数据库中可能不是这样。

Oracle数据库中有两个非常重要的概念。多版本multi-versioning)一词实质上指Oracle能够从数据库同时物化多个版本的数据。如果你理解了多版本如何工作,就会知道能从数据库得到什么。在进一步深入讨论Oracle如何实现多版本之前,下面用我认为最简单的一个方法来演示Oracle中的多版本:

在前面的例子中,我创建了一个测试表T,并把ALL_USERS表的一些数据加载到这个表中。然后在这个表上打开一个游标。在此没有从该游标获取数据,只是打开游标而已。

注意    要记住,Oracle并不“回答”这个查询。打开游标时,Oracle不复制任何数据,你可以想想看,即使一个表有十亿条记录,是不是也能很快就打开游标?没错,游标会立即打开,它会边行进边回答查询。换句话说,只是在你获取数据时它才从表中读数据。

在同一个会话中(或者也可以在另一个会话中;这同样能很好地工作),再从该表删除所有数据。甚至COMMIT提交了删除所做的工作。记录行都没有了,但是真的没有了吗?实际上,还是可以通过游标获取到数据。OPEN命令返回的结果集在打开的那一刻(时间点)就已经确定。打开时,我们根本没有碰过表中的任何数据块,但答案已经是铁板钉钉的了。获取数据之前,我们无法知道答案会是什么;不过,从游标角度看,结果则是固定不变的。打开游标时,并非Oracle将所有数据复制到另外某个位置;实际上是DELETE命令为我们把数据保留下来,把它放在一个称为undoundo segment)的数据区,这个数据区也称为回滚段rollback segment)。

读一致性(read-consistency)和多版本就是这么回事。如果你不了解Oracle的多版本机制是怎样工作的,不清楚这意味着什么,你就不可能充分利用Oracle,也不可能在Oracle上开发出正确的应用(也就是说,能确保数据完整性的应用)。

1. 多版本和闪回

过去,Oracle总是基于查询的某个时间点来做决定(从这个时间点开始查询是一致的)。也就是说,Oracle会保证打开的结果集肯定是以下两个时间点之一的当前结果集:

q  游标打开时的时间点。这是READ COMMITTED隔离模式的默认行为,该模式是默认的事务模式(第7章将介绍READ COMMITTEDREAD ONLYSERIALIZABLE事务级别之间的差别)。

q  查询所属事务开始的时间点。这是READ ONLYSERIALIZABLE隔离级别中的默认行为。

不过,从Oracle9i开始,情况要灵活得多。实际上,我们可以指示Oracle提供任何指定时间的查询结果(对于回放的时间长度有一些合理的限制;当然,这要由你的DBA来控制),这里使用了一种称为闪回查询flashback query)的特性。

请考虑以下例子。首先得到一个SCN,这是指系统修改号(System Change Number)或系统提交号(System Commit Number);这两个术语可互换使用。SCNOracle的内部时钟:每次发生提交时,这个时钟就会向上滴答(递增)。实际上也可以使用日期或时间戳,不过这里SCN很容易得到,而且相当准确:

现在可以让Oracle提供SCN值所表示时间点的数据。以后再查询Oracle时,就能看看这一时刻表中的内容。首先来看EMP表中现在有什么:

下面把这些信息都删除,并验证数据是否确实“没有了”:

此外,使用闪回查询(即AS OF SCNAS OF TIMESTAMP子句),可以让Oracle告诉我们SCN值为33295399的时间点上表中有什么:

不仅如此,这个功能还能跨事务边界。我们甚至可以在同一个查询中得到同一个对象在“两个时间点”上的结果!因此可以做些有意思的事情:

如果你使用的是Oracle 10g 及以上版本,就有一个“闪回”(flashback)命令,它使用了这种底层多版本技术,可以把对象返回到以前某个时间点的状态。在这个例子中,可以将EMP表放回到删除信息前的那个时间点:

注意    如果你得到一个错误ORA-08189: cannot flashback the table because row movement is not enabled using the FLASHBACK command”(ORA-08189无法闪回表因为不支持使用FLASHBACK命令完成行移动),就必须先执行一个命令ALTER TABLE EMP ENABLE ROW MOVEMENT。这个命令的作用是,允许Oracle修改分配给行的rowid。在Oracle中,插入一行时就会为它分配一个rowid,而且这一行永远拥有这个rowid。闪回表处理会对EMP完成DELETE,并且重新插入行,这样就会为这些行分配一个新的rowid。要支持闪回就必须允许Oracle执行这个操作。

2. 读一致性和非阻塞读

下面来看多版本、读一致查询以及非阻塞读的含义。如果你不熟悉多版本,下面的代码看起来可能有些奇怪。为简单起见,这里假设我们读取的表在每个数据库块(数据库中最小的存储单元)中只存放一行,而且这个例子要全面扫描这个表。

我们查询的表是一个简单的ACCOUNTS表。其中包含了一家银行的账户余额。其结构很简单:

在实际中,ACCOUNTS表中可能有上百万行记录,但是为了力求简单,这里只考虑一个仅有4行的表(第7章还会更详细地分析这个例子),如表1-1所示。

1-1  ACCOUNTS 表的内容

   

账户余额

1

123

$500.00

2

234

$250.00

3

345

$400.00

4

456

$100.00

我们可能想运行一个日报表,了解银行里有多少钱。这是一个非常简单的查询:

当然,这个例子的答案很明显:$1 250.00。不过,如果我们现在读了第1行,准备读第2行和第3行时,一台自动柜员机(ATM)针对这个表发生了一个事务,将$400.00从账户123转到了账户456,又会怎么样呢?查询会计算出第4行的余额为$500.00,最后就得到了$1 650.00,是这样吗?当然,应该避免这种情况,因为这是不对的,任何时刻账户余额列中的实际总额都不是这个数。读一致性就是Oracle为避免发生这种情况所采用的办法,你要了解,与几乎所有的其他数据库相比,Oracle采用的方法有什么不同。

在几乎所有的其他数据库中,如果想得到“一致”和“正确”的查询答案,就必须在计算总额时锁定整个表,或者在读取记录行时对其锁定。这样一来,获取结果时就可以防止别人再做修改。如果提前锁定表,就会得到查询开始时数据库中的结果。如果在读取数据时锁定(这通常称为共享读锁shared read lock),可以防止更新,但不妨碍读取器访问数据),就会得到查询结束时数据库中的结果。这两种方法都会大大影响并发性。由于存在表锁,查询期间会阻止对整个表进行更新(对于一个仅有4行的表,这可能只是很短的一段时间,但是对于有上百万行记录的表,可能就是几分钟之多)。“边读边锁定”的办法也有问题,不允许对已经读取和已经处理过的数据再做更新,实际上这会导致查询与其他更新之间产生死锁。

我曾经说过,如果你没有理解多版本的概念,就无法充分利用Oracle。下面告诉你一个原因。Oracle会利用多版本来得到结果,也就是查询开始时那个时间点的结果,然后完成查询,而不做任何锁定(转账事务更新第1行和第4行时,这些行会对其他写入器锁定,但不会对读取器锁定,如这里的SELECT SUM...查询)。实际上,Oracle根本没有“共享读”锁(这是其他数据库中一种常用的锁),因为这里不需要。对于可能妨碍并发性的一切因素,只要能去掉的,Oracle都已经去掉了。

我见过这样一些实际案例,开发人员没有很好地理解Oracle的多版本功能,他编写的查询报告将整个系统紧紧地锁起来。之所以会这样,主要是因为开发人员想从查询得到读一致的(即正确的)结果。这个开发人员以前用过其他一些数据库,在这些数据库中,要做到这一点都需要对表锁定,或者使用一个SELECT ... WITH HOLDLOCK(这是SQL Server中的一种锁定机制,可以边读取边以共享模式对行锁定)。所以开发人员想在运行报告前先对表锁定,或者使用SELECT ... FOR UPDATE(这是Oracle中与holdlock最接近的命令)。这就导致系统实质上会停止处理事务,而这完全没有必要。

那么,如果Oracle读取时不对任何数据锁定,那它又怎么能得到正确、一致的答案($1 250.00)呢?换句话说,如何保证得到正确的答案同时又不降低并发性?秘密就在于Oracle使用的事务机制。只要你修改数据,Oracle就会创建撤销(undo)条目。这些undo条目写至undo(撤销段,undo segment)。如果事务失败,需要撤销,Oracle就会从这个回滚段读取“之前”的映像,并恢复数据。除了使用回滚段数据撤销事务外,Oracle还会用它撤销读取块时对块所做的修改,使之恢复到查询开始前的时间点。这样就能摆脱锁来得到一致、正确的答案,而无需你自己对任何数据锁定。

所以,对我们这个例子来说,Oracle得到的答案如表1-2所示。

1-2 实际的多版本例子

   

   

转账事务

T1

读第1行;到目前为止sum = $500

 

T2

更新第1行;对第1行加一个排他锁(也称独占锁,exclusive lock),阻止其他更新

1行现在有$100

T3

读第2行;到目前为止sum = $750

 

T4

读第3行;到目前为止sum = $1 150

 

T5

 

更新第4行;对第4行加一个排他锁,阻止其他更新(但不阻止读操作)。第4行现在有$500

T6

读第4行,发现第4行已修改。这会将块回滚到T1时刻的状态。查询从这个块读到值$100

 

T7

得到答案$1 250

 

T6时,Oracle有效地“摆脱”了事务加在第4行上的锁。非阻塞读是这样实现的:Oracle只看数据是否改变,它并不关心数据当前是否锁定(锁定意味着数据已经改变)。Oracle只是从回滚段中取回原来的值,并继续处理下一个数据块。

下一个例子也能很好地展示多版本。在数据库中,可以得到同一个信息处于不同时间点的多个版本。Oracle能充分使用不同时间点的数据快照来提供读一致查询和非阻塞查询。

数据的读一致视图总是在SQL语句级执行。SQL语句的结果对于查询开始的时间点来说是一致的。正是因为这一点,所以下面的语句可以插入可预知的数据集:

SELECT * FROM T的结果在查询开始执行时就已经确定了。这个SELECT并不看INSERT生成的任何新数据。倘若真的能看到新插入的数据,这条语句就会陷入一个无限循环。如果INSERTT中生成了更多的记录行,而SELECT也随之能“看到”这些新插入的行,前面的代码就会建立数目未知的记录行。如果表T刚开始有10行,等结束时T中可能就会有202123或无限行记录。这完全不可预测。Oracle为所有语句都提供了这种读一致性,所以如下的INSERT也是可预知的:

这个INSERT语句得到了T的一个读一致视图。它看不到自己刚刚插入的行,而只是插入INSERT操作刚开始时表中已有的记录行。许多数据库甚至不允许前面的这种递归语句,因为它们不知道到底可能插入多少行。

所以,如果你用惯了其他数据库,只熟悉这些数据库中处理查询一致性和并发性的方法,或者你根本没有接触过这些概念(也就是说,你根本没有使用数据库的经验),现在应该知道,理解Oracle的做法对你来说有何等重要的意义。要想最大限度地发挥Oracle的潜能,以及为了实现正确的代码,你必须了解Oracle中的这些问题是怎么解决的(而不是其他数据库中是如何实现的)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值