学习《Oracle 9i10g编程艺术》的笔记 (三)

来源:互联网 发布:淘宝怎么修改主题颜色 编辑:程序博客网 时间:2024/05/01 20:11

 1.多版本:读一致查询  非阻塞查询

Oracle 采用了一种
多版本、读一致(read-consistent)的并发模型。再次说明,我们将在第7 章更详细地介绍有关的技术。
不过,实质上讲,Oracle 利用这种机制提供了以下特性:
读一致查询:对于一个时间点(point in time),查询会产生一致的结果。
非阻塞查询:查询不会被写入器阻塞,但在其他数据库中可能不是这样。
Oracle 数据库中有两个非常重要的概念。多版本(multi-versioning)一词实质上指Oracle 能够从
数据库同时物化多个版本的数据。如果你理解了多版本如何工作,就会知道能从数据库得到什么。在进一
步深入讨论Oracle 如何实现多版本之前,下面用我认为最简单的一个方法来演示Oracle 中的多版本:
( p_resource_name, p_start_time, p_end_time );
end schedule_resources;
ops$tkyte@ORA10G> create table t
2 as
3 select *
4 from all_users;

Table created.
ops$tkyte@ORA10G> variable x refcursor
ops$tkyte@ORA10G> begin
2 open :x for select * from t;
3 end;
4 /
PL/SQL procedure successfully completed.
ops$tkyte@ORA10G> delete from t;
28 rows deleted.
ops$tkyte@ORA10G> commit;
Commit complete.
ops$tkyte@ORA10G> print x
USERNAME USER_ID CREATED
------------------------------ -------------- ---------------
BIG_TABLE 411 14-NOV-04
OPS$TKYTE 410 14-NOV-04
DIY 69 26-SEP-04
...
OUTLN 11 21-JAN-04
SYSTEM 5 21-JAN-04

在前面的例子中,我创建了一个测试表T,并把ALL_USERS 表的一些数据加载到这个表中。然后在这
个表上打开一个游标。在此没有从该游标获取数据,只是打开游标而已。
注意要记住,Oracle 并不“回答”这个查询。打开游标时,Oracle 不复制任何数据,你可以想想看,
即使一个表有十亿条记录,是不是也能很快就打开游标?没错,游标会立即打开,它会边行进边
回答查询。换句话说,只是在你获取数据时它才从表中读数据。
在同一个会话中(或者也可以在另一个会话中;这同样能很好地工作),再从该表删除所有数据。甚
至用COMMIT 提交了删除所做的工作。记录行都没有了,但是真的没有了吗?实际上,还是可以通过游标获
取到数据。OPEN 命令返回的结果集在打开的那一刻(时间点)就已经确定。打开时,我们根本没有碰过表
中的任何数据块,但答案已经是铁板钉钉的了。获取数据之前,我们无法知道答案会是什么;不过,从游
标角度看,结果则是固定不变的。打开游标时,并非Oracle 将所有数据复制到另外某个位置;实际上是
DELETE 命令为我们把数据保留下来,把它放在一个称为undo 段(undo segment)的数据区,这个数据区
也称为回滚段(rollback segment)。

 

 

 

 

我们查询的表是一个简单的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 的多版本功能,他编写的查询报告将整
create table accounts
( account_number number primary key,
account_balance number
);
select sum(account_balance) from accounts;

个系统紧紧地锁起来。之所以会这样,主要是因为开发人员想从查询得到读一致的(即正确的)结果。这
个开发人员以前用过其他一些数据库,在这些数据库中,要做到这一点都需要对表锁定,或者使用一个
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 生成的任何新
数据。倘若真的能看到新插入的数据,这条语句就会陷入一个无限循环。如果INSERT 在T 中生成了更多的
记录行,而SELECT 也随之能“看到”这些新插入的行,前面的代码就会建立数目未知的记录行。如果表T
刚开始有10 行,等结束时T 中可能就会有20、21、23 或无限行记录。这完全不可预测。Oracle 为所有语
句都提供了这种读一致性,所以如下的INSERT 也是可预知的:
这个INSERT 语句得到了T 的一个读一致视图。它看不到自己刚刚插入的行,而只是插入INSERT 操作
刚开始时表中已有的记录行。许多数据库甚至不允许前面的这种递归语句,因为它们不知道到底可能插入
多少行

原创粉丝点击