我们的技术专家回答关于游标范围(extent)和间隔的问题 是不是从Oracle第版以后的版本隐式游标得到了优化不会两次取数据?还有为什么当表T在列X上有一个索引时下面的隐式游标比显式游标运行得更快而没有索引时是显式游标运行得较快呢? Implicit Cursor: Select x into y from T where x = j; Explicit Cursor: cursor c(p number) is select x from blah where x = p; open c(j); fetch c into y; close c; 为了让每个人都了解显式游标和隐式游标是什么我先简单介绍一下它们的定义 通常隐式游标是指程序员并不显式声明打开从中取数据和关闭的那些游标这些操作都是隐式的因此在上面的例子中SELECT X INTO Y查询就是一个隐式游标对于它来说并没有cursor cursor_name is 这样的定义语句相反第二个例子是典型的显式关标程序员显式地声明打开取数据和关闭它 在PL/SQL中隐式游标比显式游标运行得更快是一个事实在Oracle 版之前的版本中就是这样事实上我在Oracle 版中就测试过这样的情况并得到了同样的结论(这些测试请参见/~tkyte/l)隐式游标运行得更快的原因(FOR LOOP隐式游标和SELECT INTO隐式游标)是PL/SQL引擎只需要解释和执行很少的代码一般来说PL/SQL引擎在后台做的越多程序就运行地越快上面的隐式游标只使用了一行PL/SQL代码显式游标至少使用了三行代码如果要正确地运行实际上要使用行代码你的显式代码并不像隐式游标那样运行它要确保你得到一条且只得到一条记录你的显式代码缺少了许多你要做的工作为了精确地比较你的两个游标例子你的显式代码应该被扩展出以下几行 open c(j); fetch c into y; if ( c%notfound ) then raise NO_DATA_FOUND; end if; fetch c into y; if ( c%found ) then raise TOO_MANY_ROWS; end if; close c; 如果这就是你的显式游标你会发现在所有情况下显式游标都运行得比较慢甚至于无论你的例子中有没有索引都是这样 那么你的问题的症结所在是为什么在你的例子中没有索引时隐式游标好像运行地非常慢然而当存在一个索引的时候隐式游标却运行得较快呢?答案在于全表扫描事实上在得到一条记录后你的显式测试就停止了我将给出一个例子来向你展示它们之间的不同之处 SQL> create table t ( x int ) pctfree pctused ; Table created SQL> insert into t select rownum from all_objects; rows created SQL> analyze table t compute statistics; Table analyzed SQL> select blocks empty_blocks num_rows from user_tables where table_name = T; BLOCKS EMPTY_BLOCKS NUM_ROWS 我创建了一个有许多数据块的表值pctfree 为随后更新数据保留了%的块作为空闲空间因此即使表中的数据量很小表本身也相当大接着我通过INSERT把值一直到严格按顺序插入到表中因此X=在该表的第一个块中而X=在表中相当接近表的最后一个块 接下来我将运行一个小PL/SQL块它会显示各种隐式和显式游标对数据进行一致读的次数因为没有索引查询将对整个表进行全面扫描一旦我运行这个程序然后评审查询结果将很容易对性能的差异进行量化 SQL> declare l_last_cgets number default ; l_xnumber; cursor c( p_x in number ) is select x from t where x = p_x;
procedure cgets( p_msg in varchar ) is l_value number; begin select bvalue into l_value from v$statname a v$mystat b where astatistic# = bstatistic# and aname = consistent gets;
dbms_outputput_line( p_msg ); dbms_outputput_line (Incremental cgets: || to_char(l_valuel_last_cgets ) ); l_last_cgets := l_value; end;
begin cgets(Starting);
open c(); fetch c into l_x; close c; cgets(Explicit to find X= || stop at first hit );
open c(); fetch c into l_x; fetch c into l_x; close c; cgets(Explicit to find X= || check for dups );
select x into l_x from t where x = AND rownum = ; cgets(Implicit to find X= || stop at first hit );
select x into l_x from t where x = ; cgets(Implicit to find X= || check for dups );
open c(); fetch c into l_x; close c; cgets(Explicit to find X=);
select x into l_x from t where x = ; cgets(Implicit to find X=); end; / Starting Incremental cgets: Explicit to find X= stop at first hit Incremental cgets:&nb sp; Explicit to find X= check for dups Incremental cgets: Implicit to find X= stop at first hit Incremental cgets: Implicit to find X= check for dups Incremental cgets: Explicit to find X= Incremental cgets: Implicit to find X= Incremental cgets: PL/SQL procedure successfully completed 现在你就可以明白在你的例子中为什么显式游标好像比隐式游标运行得更快了当我使用显式游标进行测试的时候只取一次数据X=为了找到答案查询只需要扫描非常少的块(很少的一致的读次数)然而只要我使显式游标来进行隐式游标的工作检查确保没有其他记录满足同一条件你就会看到显式游标检查表中的每一个块现在我接着说隐式游标通过使用ROWNUM=看看它是否也会在找到第一条符合条件的记录时停下来它和显式游标做相同的工作量当它检查表中的第二行是否符合条件时你会看到它同显式游标一样进行相同次数的一致读它也不得不对表进行全面扫描以核定只有一行X= 最有趣的是当我查询X=的时候因为那行接近表的结尾所以无论我采用什么方法两个查询的工作量都差不多为了找到满足条件的第一行它们都必须扫描几乎整个表 现在如果在X上有一个索引两个查询都会使用索引范围扫描而且两个查询都不必对表进行全面扫描便能快速地发现只有一行满足条件 这就解释了你的游标行为SELECT INTO检查第二行但显式游标却不这么做如果你对应地进行比较第二次显式地取数据或者把rownum = 添加到SELECT INTO语句中--你就会发现两个游标的工作量相同 简而言之隐式游标更好它们比使用显式游标的相同代码运行地更快更容易编码(需要键入的代码更少)而且我个人认为使用隐士游标的代码更容易读也更容易理解 小中和大 在我们的新应用程序中我们设计了数据库并创建了数据模型甚至还估计了表的大小并为每个标指定了存储参数但现在我们的数据库管理员告诉我们将给我们三个表空间范围大小统一为K的TS_small表空间范围大小统一为MB的TS_med表空间和范围大小统一为MB的TS_large表空间他们告诉我们在TS_small中创建小于MB的表在TS_med中创建小于MB的表在TS_large中创建大于MB的表另外他们不希望我们对表使用任何存储参数对索引也是这样这好像并不合理因为对于一个预计大小为MB的表我们应把它放在TS_med中接下来如果我们在那个表空间中创建它它会占个范围!数据库管理员声称许多测试已经证明这种设计提供了最佳的性能并可以防止碎片我的问题是他们说的对吗?我担心对象会有太多的范围 看来他们已经读过asktom Web站点()和互联网讨论组的相关内容并发现了好的建议从他们的数字看我注意到他们允许一个表占用的最大空间是GB可以有个或更少的范围假设上百个(或者上千个)范围不会影响运行时数据操纵语言(DML)的性能我会说他们做得非常好 他们的前提都是正确的 |