Oracle9i&10g的编程艺术-开发

Oracle中主要有9种表类型:
? 堆组织表(heap organized table):这些就是“普通”的标准数据库表。数据以堆的方式管理。增加数据时,会使用段中找到的第一个能放下此数据的自由空间。从表中删除数据时,则 允许以后的INSERT和UPDATE重用这部分空间。这就是这种表类型中的“堆”这个名词的由来。堆(heap)是一组空间,以一种有些随机的方式使 用。
? 索引组织表(index organized table):这些表按索引结构存储。这就强制要求行本身有某种物理顺序。在堆中,只要放得下,数据可以放在任何位置;而索引组织表(IOT)有所不同,在IOT中,数据要根据主键有序地存储。
? 索引聚簇表(index clustered table):聚簇(cluster)是指一个或多个表组成的组,这些表物理地存储在相同的数据库块上,有相同聚簇键值的所有行会相邻地物理存储。这种结构可以实现两个目标。首先,多个表可以物理地存储在一起。一般而言,你可能认为一个表的数据就在一个数据库块上,但是对于聚簇表,可能把多个表的数据存储 在同一个块上。其次,包含相同聚簇键值(如DEPTNO=10)的所有数据会物理地存储在一起。这些数据按聚簇键值“聚簇”在一起。聚簇键使用B*树索引建立。
? 散列聚簇表(hash clustered table):这些表类似于聚簇表,但是不使用B*树索引聚簇键来定位数据,散列聚簇将键散列到聚簇上,从而找到数据应该在哪个数据库块上。在散列聚簇 中,数据就是索引(这是隐喻的说法)。如果需要频繁地通过键的相等性比较来读取数据,散列聚簇表就很适用。
? 有序散列聚簇表(sorted hash clustered table):这种表类型是Oracle 10g中新增的,它结合了散列聚簇表的某些方面,同时兼有IOT的一些方面。其概念如下:你的行按某个键值(如CUSTOMER_ID)散列,而与该键相 关的一系列记录以某种有序顺序到达(因此这些记录是基于时间戳的记录),并按这种有序顺序处理。例如,客户在你的订单输入系统中下订单,这些订单会按先进 先出(first in, first out, FIFO)的方式获取和处理。在这样一个系统中,有序散列聚簇就是适用的数据结构。
? 嵌套表(nested table):嵌套表是Oracle对象关系扩展的一部分。它们实际上就是系统生成和维护的父/子关系中的子表。嵌套表的工作类似于SCOTT模式中的 EMP和DEPT。可以认为EMP是DEPT表的子表,因为EMP表有一个指向DEPT的外键DEPTNO。嵌套表与子表的主要区别是:嵌套表不像子表 (如EMP)那样是“独立”表。
? 临时表(temporary table):这些表存储的是事务期间或会话期间的“草稿”数据。临时表要根据需要从当前用户的临时表空间分配临时区段。每个会话只能看到这个会话分配的区段;它从不会看到其他任何会话中创建的任何数据。
? 对象表(object table):对象表基于某种对象类型创建。它们拥有非对象表所没有的特殊属性,如系统会为对象表的每一行生成REF(对象标识符)。对象表实际上是堆组织表、索引组织表和临时表的特例,还可以包含嵌套表作为其结构的一部分。
? 外部表(external table):这些表并不存储在数据库本身中,而是放在数据库之外,即放在平常的操作系统文件中。在Oracle9i及以上版本中,利用外部表可以查询数 据库之外的一个文件,就好像这个文件也是数据库中平常的表一样。外部表对于向数据库加载数据最有用(外部表是非常强大的数据加载工具)。Oracle 10g则更进一步,还引入了一个外部表卸载功能,在不使用数据库链接的情况下,这为在Oracle数据库之间移动数据提供了一种简单的方法。我们将在第 15章更详细地讨论外部表。

最常见的段类型如下:
? 聚簇(cluster):这种段类型能存储表。有两种类型的聚簇:B*树聚簇和散列聚簇。聚簇通常用于存储多个表上的相关数据,将其“预联结”存储到同一个数据库块上;还可以用于存储一个表的相关信息。“聚簇”这个词是指这个段能把相关的信息物理的聚在一起。
? 表(table):表段保存一个数据库表的数据,这可能是最常用的段类型,通常与索引段联合使用。
? 表分区(table partition)或子分区(subpartition):这种段类型用于分区,与表段很相似。分区表由一个或多个分区段(table partition segment)组成,组合分区表则由一个或多个表子分区段(table subpartition segment)组成。
? 索引(index):这种段类型可以保存索引结构。
? 索引分区(index partition):类似与表分区,这种段类型包含一个索引的某个片。分区索引由一个或多个索引分区段(index partition segment)组成。
? Lob分 区(lob partition)、lob子分区(lob subpartition)、lob索引(lobindex)和lob段(lobsegment):lobindex和lobsegment段保存大对象 (large object或LOB)的结构。对包含LOB的表分区时,lobsegment也会分区,lob分区段(lob partition segment)正是用于此。有意思的是,并没有一种lobindex分区段(lobindex partition segment)类型——不论出于什么原因,Oracle将分区lobindex标记为一个索引分区(有人很奇怪为什么要另外给lobindex取一个特 殊的名字!)。
? 嵌套表(nested table):这是为嵌套表指定的段类型,它是主/明细关系中一种特殊类型的“子”表,这种关系随后将详细讨论。
? 回滚段(rollback)和Type2 undo段:undo数据就存储在这里。回滚段是DBA手动创建的段。Type2 undo段由Oracle自动创建和管理。

从Oracle 9i开始,管理段空间有两种方法:
? 手动段空间管理(Manual Segment Space Management):由你设置FREELISTS、FREELIST GROUPS、PCTUSED和其他参数来控制如何分配、使用和重用段中的空间。在这一章中我会把这种空间管理方法称为MSSM,但是要记住,这是一个我 自造的缩写,Oracle文档中没有这个缩写。
? 自动段空间管理(Automatic Segment Space Management, ASSM):你只需控制与空间使用相关的一个参数:PCTFREE。创建段时也可以接受其他参数,但是这些参数将被忽略。

对于MSSM,倘若将FREELISTS参数设置为默认值1,可能会出现,如果你的段是插入/更新新密集的(有大量插入/更新操作),对自由空间的分配就会存在竞 争。Oracle要在表中插入一行,或更新一个索引键条目,或者由于更新一行而导致这一行迁移时(稍后还会更多地介绍这方面的内容),可能需要从与这个段 相关的自由块列表中得到一个块。如果只有一个自由块列表,一次就只有一个事务能查看和修改这个列表,事务之间必须相互等待。在这种情况下,如果有多个 FREELISTS和FREELIST GROUPS,就能提高并发性,因为事务可以分别查看不同的列表,而不会相互竞争。

在存储/段特征这方面,应用于ASSM段的存储设置只有:
? BUFFER_POOL
? PCTFREE
? INITRANS
? MAXTRANS(仅用于9i;在10g中,所有段都会忽略这个参数)

高水位线(high-water mark,HWM)很重要,因为Oracle在全面扫描段时会扫描HWM之下的所有块,即使其中不包含任何数据。
在 一个MSSM表空间中,段只有一个HWM。不过,在ASSM表空间中,除了一个HWM外,还有一个低HWM(见图10-2)。在MSSM中,HWM推进时 (例如,插入行时),所有块都会并立即有效,Oracle可以安全地读取这些块。不过,对于ASSM,HWM推进时,Oracle并不会立即格式化所有 块,只有在第一次使用这些块时才会完成格式化,以便安全地读取。所以,全面扫描一个段时,必须知道要读取的块是否“安全”或是否格式化,(这说明,其中不 包含有意义的信息,不能对其进行处理)。为了避免表中每一个块都必须经过这种安全/不安全检查,Oracle同时维护了一个低HWM和一个HWM。 Oracle会全表扫描至HWM,对于低HWM以下的所有块会直接读取并加以处理。而对介于低HWM和HWM之间的块,则必须更加小心,需要参考管理这些 块所用的ASSM位图信息来查看应该读取哪些块,而哪些块应该被忽略。

使用MSSM表空间时,Oracle会在自由列表(freelist)中为有自由空间的对象维护HWM一些的块。
freelists组和freelist组在ASSM表空间中根本就没有;仅MSSM表空间使用这个技术。
每 个对象都至少有一个相关的freelist,使用块时,可能会根据需要把块放在freelist上或者从freelist删除。需要说明的重要一点是,只 有位于HWM以下的对象块才会出现在freelist中。仅当freelist为空时才会使用HWM之上的块,此时Oracle会推进HWM,并把这些块 增加到freelist中,采用这种方式,Oracle会延迟到不得已时才增加对象的HWM。
只需把freelist设置得相当高,然后就万事大吉了,是这样吗?当然不是,哪有这么容易。使用多个freelist时,有一个主freelist,还有 一些进程freelist。如果一个段只有一个freelist,那么主freelist和进程freelist就是这同一个自由列表。如果你有两个 freelist,实际上将有一个主freelist和两个进程freelist。对于一个给定的会话,会根据其会话ID的散列值为之指定一个进程 freelist。目前,每个进程freelist都只有很少的块,余下的自由块都在主freelist上。使用一个进程freelist时,它会根据需 要从主freelist拉出一些块。如果主freelist无法满足空间需求,Oracle就会推进HWM,并向主freelist中增加空块。过一段时 间后,主freelist会把其存储空间分配多个进程freelist(再次说明,每个进程freelist都只有为数不多的块)。因此,每个进程会使用 一个进程freelist。它不会从一个进程freelist到另一个进程freelist上寻找空间。
一 般而言,PCTFREE参数用来告诉Oracle应该在块上保留多少空间来完成将来的更新。默认情况下,这个值是10%。如果自由空间的百分比高于 PCTFREE中的指定值,这个块就认为是“自由的”。PCTUSED则告诉Oracle当前不“自由”的一个块上自由空间百分比需要达到多大才能使它再 次变为自由的。默认值是40%。

行迁移(row migration)是指由于某一行变得太大,无法再与其余的行一同放在创建这一行的块中(块中已经放不下这一行),这就要求这一行离开原来的块。


那么对于堆表来说,需要注意哪些重要的选项呢?在我看来,对于ASSM有两个重要选项,对于MSSM,重要选项有4个:
FREELISTS: 仅适用于MSSM。每个表都会在一个freelist上管理堆中分配的块。一个表可以有多个freelist。如果你认定会有多个并发用户对表执行大量的 插入,配置多个freelist可能会大大地改善性能(可能要以额外的存储空间为代价)。这个设置对性能可能产生的影响请参见“FREELISTS”一节 中的讨论和有关例子。
PCTFREE:ASSM和MSSM都适用。在INSERT过程中,会测量块的充满程度。如前所示,根据块当前充满的程度,这个参数用于控制能否将一行增加到一个块上。这个选项还可以控制因后续更新所导致的行迁移,要根据将如何使用表来适当地设置。
PCTUSED:仅适用于MSSM。度量一个块必须为多空才允许再次插入行。如果块中已用的空间小于PCTUSED,就可以插入新行了。同样地,类似于PCTFREE,必须考虑你将如何使用表,从而适当地设置这个选项。
INITRANS:ASSM 和MSSM都适合。为块初始分配的事务槽数。如果这个选项设置得太低(默认值为2,这也是最小值),可能导致多个用户访问的一个块上出现并发问题。如果一 个数据块机会已满,而且事务表无法动态扩展,会话就会排队等待这个块,因为每个并发事务都需要一个事务槽。如果你认为会对同样的块完成多个并发更新,就应 该考虑增大这个值。

IOT提供了以下好处:
? 提供缓冲区缓存效率,因为给定查询的缓存中需要的块更少。
? 减少缓冲区缓存访问,这会改善可扩缩性。
? 获取数据的工作总量更少,因为获取数据更快。
? 每个查询完成的物理I/O更少,因为对于任何给定的查询,需要的块更少,而且对地址记录的一个物理I/O很可能可以获取所有地址(而不只是其中一个地址,但堆表实现就只是获取一个地址)。

IOT(索引组织表)没有PCTUSED子句,只有PCTFREE参数。这是因为,索引是一个复杂的数据结构,它不像堆那样随机组织,所以 数据必须按部就班地存放到它该去的地方去。在堆中,块只是有时能插入新行,而索引则不同,块总是可以插入新的索引条目。如果每个数据(根据它的值)属于一 个给定块,在总会放在那个块上,而不论这个块多满或者多空。另外,只是在索引结构中创建对象和填充数据时才会使用PCTFREE。其用法与堆组织表中的用 法不同。PCTFREE会在新创建的索引上预留空间,但是对于以后对索引的操作不预留空间,这与不使用PCTUSED的原因是一样的。堆组织表上关于 freelist的考虑同样完全适用于IOT。

NOCOMPRESS选项对索引一般都可用。它告诉Oracle把每个值分别存储在各个索引条目中(也就是不压缩)。如果对象 的主键在A、B和C列上,A、B和C的每一次出现都会物理地存储。NOCOMPRESS反过来就是COMPRESS N,在此N是一个整数,表示要压缩的列数。这样可以避免重复值,并在块级提取“公因子”(factor out)。这样在A的值(以及B的值)重复出现时,将不再物理地存储它们。

现在可以测量使用compress选项能节省多少空间。为此我们将使用ANALYZE INDEX VALIDATE STRUCTURE命令。这个命令会填写一个名为INDEX_STATS的动态性能视图,其中最多只包含一行,即这个ANALYZE命令最后一次执行的信息。其中OPT_CMPR_COUNT(最优压缩数)列要说 的是:“如果你把这个索引置为COMPRESS 2,就会得到最佳的压缩”。OPT_CMPR_PCTSAVE(最优的节省压缩百分比)则是说,如果执行COMPRESS 2,就能节省大约1/3的存储空间,索引只会使用现在2/3的磁盘空间。

PCTTHRESHOLD、OVERFLOW和INCLUDING目标是让索引叶子块(包含具体索引数据的块)能够高效地存储数据。OVERFLOW子句允许你建立另一个段(这就使得IOT成为一个多段对象,就像有一个CLOB列一样),如果IOT的行数据变得太大,就可以溢出到这个段中。注意:构成主键的列不能溢出,它们必须直接放在叶子块上。使用溢出段的条件可以采用两种方式来指定:
? PCTTHRESHOLD:行中的数据量超过块的这个百分比时,行中余下的列将存储在溢出段中。所以,如果PCTTHRESHOLD是10%,而块大小是8KB,长度大于800字节的行就会把其中一部分存储在别处,而不能在索引块上存储。
? INCLUDING:行中从第一列直到INCLUDING子句所指定列(也包括这一列)的所有列都存储在索引块上,余下的列存储在溢出段中。

IOT本身可以有一个索引,就像在索引之上再加索引,这称为二次索引(secondary index)。 正常情况下,索引包含了所指向的行的物理地址,即rowid。而IOT二次索引无法做到这一点;它必须使用另外某种方法来指示行的地址。这是因为IOT中 的行可以大量移动,而且它不像堆组织表中的行那样“迁移”。IOT中的行肯定在索引结构中的每个位置上,这取决于它的主键值;只有当索引本身的大小和形状发生改变时行才会移动。为了适应这种情况,Oracle引入了一个逻辑rowid(logical rowid)。 这些逻辑rowid根据IOT主键建立。

聚簇(cluster)是指:如果一组表有一些共同的列,则将这样一组表存储在相同的数据库块中;聚簇还表示把相关的数据存储在同一个块上。利用聚簇,一个块可能包含多个表的数据。从概念上讲,这是将数据“预联结”地存 储。聚簇还可以用于单个表,可以按某个列将数据分组存储。聚簇并不是有序地存储数据(这是IOT的工作),它是按每个键以聚簇方式存储数据,但数据存储在堆中。

在对象中创建表的一个聚簇很直接。对象的存储定义(PCTFREE、PCTUSED、INITIAL等)与 CLUSTER相关,而不是与表相关。这是有道理的,因为聚簇中会有多个表,而且它们在同一个块上。有多个不同的PCTFREE没有意义。因此, CREATE CLUSTER非常类似于只有很少几个列的CREATE TABLE(只有聚簇键列)

聚簇定义中的SIZE选项告诉Oracle:我们希望与每个聚簇键值关联大约1024字节的数据,Oracle会在用这个数据库块上设置来计算每个块最多能放下多少个聚簇键。
SIZE测试控制着每块上聚簇键的最大个数。这是对聚簇空间利用率影响最大的因素。如果把这个SIZE设置得太高,那么每个块上的键就会很少,我们会 不必要地使用更多的空间。如果设置得太低,又会导致数据过分串链,这又与聚簇本来的目的不符,因为聚簇原本是为了把所有相关数据都存储在一个块上。对于聚 簇来说,SIZE是最重要的参数。

为什么不是插入所有DEPT数据,然后再插入所有EMP数据呢?或者反之,先插入所有EMP数据,然后插入所有DEPT数据?为什么要像这样 按DEPTNO逐个地加载数据呢?原因就在于聚簇的设计。我们在模拟一个聚簇的大批量初始加载。如果写加载所有DEPT行,每个块上就会有7个键(根据前 面指定的SIZE 1024设置),这是因为DEPT行非常小(只有几个字节)。等到加载EMP行时,可能会发现有些部门的数据远远超过了1024字节。这样就会在那些聚簇 键块上导致过度的串链。Oracle会把包含这些信息的一组块串链或链接起来。如果同时加载对应一个给定聚簇键的所有数据,就能尽可能紧地塞满块,等空间 用完时再开始一个新块。Oracle并不是在每个块中放最多7个聚簇键值,而是会适当地尽可能多地放入聚簇键值。

什么时候要使用聚簇呢?可能反过来回答什么时候不应该使用聚簇会更容易一些:
? 如果预料到聚簇中的表会大量修改:必须知道,索引聚簇会对DML的性能产生某种负面影响(特别是INSERT语句)。管理聚簇中的数据需要做更多的工作。
? 如果需要对聚簇中的表执行全表扫描:不只是必须对你的表中的数据执行全面扫描,还必须对(可能的)多个表中的数据进行全面扫描。由于需要扫描更多的数据,所以全表扫描耗时更久。
? 如果你认为需要频繁地TRUNCATE和加载表:聚簇中的表不能截除。这是显然的,因为聚簇在一个块上存储了多个表,必须删除聚簇表中的行。
因此,如果数据主要用于读(这并不表示“从来不写”;聚簇表完全可以修改),而且要通过索引来读(可以是聚簇键索引,也可以是聚簇表上的其他索引),另外会频繁地把这些信息联结在一起,此时聚簇就很适合。应用找出逻辑上相关而且总是一起使用的表,设计Oracle数据字典的人就是这样做的,他们把与列相关的 所有信息都聚簇在一起。

散列聚簇表(Hash clustered table) 在概念上与前面介绍的索引聚簇表非常相似,只有一个主要区别:聚簇键索引被一个散列函数所取代。表中的数据就是索引;这里没有物理索引。Oracle会取 得一行的键值,使用每个内部函数或者你提供的每个函数对其计算散列,然后使用这个散列值得出数据应该在磁盘上的哪个位置。不过,使用散列算法来定位数据有 一个副作用,如果不向表增加一个传统的索引,将无法对散列聚簇中的表完成区间扫描。

类似于编程语言中的散列表,数据库中的散列表有固定的“大小”。创建表时,必须确定这个表中将有多少个散列键(而且这个数永远不变)。但散列表的大小并不限制其中能放的行数。

创建散列聚簇时,还是使用创建索引聚簇时所用的同样的CREATE CLUSTER语句,不过选项不同。这里只是要增加一个HASHKEYS选项来指定散列表的大小。Oracle得到你的HASHKEYS值,将其“舍入” 为与之最接近的质数(散列键数总是一个质数)。然后Oracle再将SIZE参数乘以修改后的HASHKEYS值,计算出一个值。再根据这个值为聚簇分配 空间,也就是说,至少要分配这么多字节的空间。这与前面的索引聚簇有很大差别,索引聚簇会在需要时动态地分配空间,散列聚簇则要预留足够的空间来保存 (HASHKEYS/trunc(blocksize/SIZE))字节的数据。例如,如果将SIZE设置为1,500字节,而且块大小为4KB, Oracle会在每个块上存储两个键。如果你计划有1,000个HASHKEY,Oracle就分配500个块。

不同于计算机语言中的传统散列表,这里允许有散列冲突,实际上,许多情况下还需要有冲突。还是用前面的DEPT/EMP例子,可以根据DEPTNO列建立一个散列聚簇。显然,多个行会散列到同一个值,这正是你希望的(因为它们有相同的DEPTNO)。这就反映了聚簇某些方面的特点:要把 类似的数据聚簇在一起。正是由于这个原因,所以Oracle要求你指定HASHKEY(你预计一段时间会有多少个部门号)和SIZE(与各个部门号相关联 的数据量)。Oracle会分配一个散列表来保存HASHKEY个部门,每个部门有SIZE字节的数据。你想避免的是无意的散列冲突。显而易见,如果就散列表的大小设置为1,000(实际上是1,099,因为散列表的大小总是质数,而且Oracle会为你找出与之最接近的质数),而你在表中放入了 1,010个部门,就至少会存在一个冲突(两个不同部门散列到同一个值)。无意的散列冲突是要避免的,因为它们会增加开销,使块串链的可能性增加。

这里的要点是:
? 散列聚簇完成的I/O(查询列)少得多。这正是我们期望的。查询只是取随机的OBJECT_ID,对其完成散列,然后找到块。散列聚簇至少要做一次I/O来 得到数据。有索引的传统表则必须完成索引扫描,然后要根据rowid访问表,才能得到同样的答案。
? 不 论用于什么目的,散列聚簇查询与索引查询所用的CPU是一样的。执行散列是一个 CPU相当密集的操作,执行索引查询则是一个I/O密集的操作,这里要做个权衡。不过,随着用户数的增加,可以想见,散列聚簇查询能更好地扩缩,因为要想 很好地扩缩,就不能太过频繁地访问缓存区缓存。

关于散列聚簇,需要了解以下要点:
? 散 列聚簇一开始就要分配空间。Oracle根据你的HASHKEYS和SIZE来计算HASHKEYS/trunc(blocksize/SIZE),立即 分配空间,并完成格式化,一旦将第一个表放入这个聚簇中,任何全面扫描都会命中每一个已分配的块。在这方面,它与其他的所有表都不同。
? 散列聚簇中的HASHKEY数是固定大小的。除非重新聚簇,否则不能改变散列表的大小。这并不会限制聚簇中能存储的数据量,它只是限制了能为这个聚簇生成的惟一散列键的个数。如果HASHKEY值设置得太低,可能因为无意的散列冲突影响性能。
? 不能在聚簇键上完成区间扫描。诸如WHERE cluster_key BETWEEN 50 AND 60谓词条件不能使用散列算法。介于50~60之间的可能值有无限多个,服务器必须生成所有可能的值,并分别计算散列,来查看相应位置是否有数据。这是不 可能的。如果你在一个聚簇键上使用区间扫描,而且没有使用传统索引,实际上会全面扫描这个聚簇。

不把嵌套表用作一种持久存储机制,原因如下:
? 这样会增加不必要的RAW(16)列存储开销。父表和子表都有这个额外的列。父表对于其中的各个嵌套表列都有一个额外的16字节RAW字段。由于父表通常已经有一个主键(在我们这个例子中就是DEPTNO),所以完全可以在子表中使用这个键,而不必使用一个系统生成的键。
? 这会在父表上增加另一个惟一约束(相应地带领不必要的开销),而父表中通常已经有一个惟一约束。
? 如果不使用NESTED_TABLE_GET_REFS(这个方法未得到公开支持),嵌套表本身使用起来并不方便。如果是查询,可以通过消除嵌套来访问嵌套表,但是如果是大批量更新,则无法简单地消除嵌套。在实际中,表往往都会“自己”查询自己,我还没有见过这方面的例外。
如果你确实把嵌套表用作一个存储机制,一定要保证将嵌套表建立为一个IOT,以避免NESTED_TABLE_ID上索引的开销以及嵌套表本身的开销。同时由于NESTED_TABLE_ID就是IOT主键的第一列,还应该加入索引键压缩来避免冗余的NESTED_TABLE_ID(否则会重复存储)。创建语句如下:

CREATE TABLE DEPT_AND_EMP
 (DEPTNO NUMBER(2, 0),
 DNAME VARCHAR2(14),
 LOC VARCHAR2(13),
 EMPS EMP_TAB_TYPE)
 NESTED TABLE EMPS
 STORE AS EMPS_NT
 ( (empno NOT NULL, unique (empno), primary key(nested_table_id,empno))
 organization index compress 1 )
 RETURN AS VALUE;

RETURN AS VALUE这个选项用于描述如何向客户应用返回嵌套表。默认情况下,Oracle会按值把嵌套表返回给客户:具体数据会随各行传输。这个选项也可以设置为 RETURN AS LOCATOR,这说明客户会得到指向数据的一个指针,而不是数据本身。当且仅当客户对这个指针解除引用(dereference)时,才会把数据传输给 客户。因此,如果你相信客户通常不会查看对应各个父行的嵌套表(不会查看这些嵌套表中的行),就可以返回一个locator而不是值,这样可以节省网络往 返通信开销。

临时表(Temporary table)用于保存事务或会话期间的中间结果集。临时表中保存的数据只对当前会话可见,所有会话都看不到其他会话的数据;即使当前会话已经提交 (COMMIT)了数据,别的会话也看不到它的数据。对于临时表,不存在多用户并发问题,因为一个会话不会因为使用一个临时表而阻塞另一个会话。即使我们 “锁住”了临时表,也不会妨碍其他会话使用它们自己的临时表。我们在第9章了解到,临时表比常规表生成的redo少得多。不过,由于临时表必须为其中包含 的数据生成undo信息,所以也会生成一定的redo。UPDATE和DELETE会生成最多的undo;INSERT和SELECT生成的undo最少。

临时表可以是基于会话的(临时表中的数据可以跨提交存 在,即提交之前仍然存在,但是断开连接后再连接后再连接时数据就没有了),也可以是基于事务的(提交之后数据就消失)。
ON COMMIT PRESERVE ROWS子句使得这是一个基于会话的临时表。在我的会话断开连接之前,或者我通过一个DELETE或TRUNCATE物理地删除行之前,这些行会一直存在于这个临时表中。只有我的会话能看到这些行;即使我已经提交(COMMIT),其他会话也无法看到“我的”行。
ON COMMIT DELETE ROWS子句使得这是一个基于事务的临时表。我的会话提交时,临时表中的行就不见了。只需把分配给这个表的临时区段交回,这些行就会消失,在临时表的自动清除过程中不存在开销。

临时表可以有永久表的许多属性。它们可以有触发器、检查约束、索引等。但永久表的某些特性在临时表中并不支持,这包括:
? 不能有引用完整性约束。临时表不能作为外键的目标,也不能在临时表中定义外键。
? 不能有NESTED TABLE类型的列。在Oracle 9i及以前版本中,VARRAY类型的列也不允许;不过Oracle 10g中去掉了这个限制。
? 不能是IOT。
? 不能在任何类型的聚簇中。
? 不能分区。
? 不能通过ANALYZE表命令生成统计信息。

对临时表使用DBMS_STATS收集代表性统计信息有2种方法。
? 第一种方法是利用GATHER_SCHEMA_STATS或 GATHER_DATABASE_STATS以及GATHER_TABLE_STATS调用来使用DBMS_STATS。这些过程允许你传入一个参数GATHER_TEMP,这是一个布尔值,默认 为FALSE。设置为TRUE时,所有ON COMMIT PRESERVE ROWS全局临时表都会收集和存储统计信息(这个技术在ON COMMIT DELETE ROWS表上不可行)。注意,ON COMMIT PRESERVE ROWS表 会有正确的统计,但是ON COMMIT DELETE ROWS表没有。DBMS_STATS将提交,而这会擦除ON COMMIT DELETE ROWS表中的所有信息。 所以,如果使用这种方法,要注意两点:
1. 要保证在收集统计信息的会话中用代表性数据填充全局临时表。如果做不到,在DBMS_STATS看来它们就是空的。
2.22. 如果有ON COMMIT DELETE ROWS全局临时表,就不应该使用这种方法,因为这样会收集到不正确的值。
? 第二种方法是利用DBMS_STATS的一个手动过程用临时表的代表性统计信息填充数据字典。例如,如果平均来讲临时表中的行数是500,而且行的平均大小是100字节,块数为7,则只需如下使用DBMS_STATS:
begin
 dbms_stats.set_table_stats( 
 ownname => USER,
 tabname => 'T',
 numrows => 500,
 numblks => 7,
 avgrlen => 100 );
 end;

Oracle中包括如下索引:
? B*树索引:这些是我所说的“传统“索引。到目前为止,这是Oracle和大多数其他数据库中最常用的索引。B*树的构造类似于二叉树,能根据键提供一行或一个行集的快速访问,通常只需很少的读操作就能找到正确的行。不过,需要注意重要的一点,”B*树“中的”B“不代表二叉(binary),而代表平衡(balanced)。B*树索引并不是一颗二叉树,这一点在介绍如何在磁盘上物理地存储B*树时就会了解到。B*树索引有以下子类型:
1.索引组织表(index organized table):索引组织表以B*树结构存储。堆表的数据行是以一种无组织的方式存储的(只要有可用的空间,就可以放数据),而IOT与之不同,IOT中的数据要按主键的顺序存储和排序。对应用来说,IOT表现得与“常规“表并无二致;需要使用SQL来正确地访问IOT。IOT对信息获取、空间系统和OLAP应用最为有用。
2.B*树聚簇索引(B*tree cluster index)这些是传统B*树索引的一个变体(只是稍有变化)。B*树聚簇索引用于对聚簇键建立索引。在传统B*树中,键都指向一行;而B*树聚簇不同,一个聚簇键会指向一个块,其中包含与这个聚簇键相关的多行。
? 降序索引(descending index):降序索引允许数据在索引结构中按“从大到小“的顺序(降序)排序,而不是按”从小到大“的顺序(升序)排序。
? 反向键索引(reverse key index):这也是B*树索引,只不过键中的字节会“反转“。利用反向键索引,如果索引中填充的是递增的值,索引条目在索引中可以得到更均匀的分布。Oracle将数据放在索引中之前,将先把所存储数据的字节反转,这样原来可能在索引中相邻放置的值在字节反转之后就会相距很远。通过反转字节,对索引的插入就会分布到多个块上。
? 位图索引(bitmap index):在一颗B*树中,通常索引条目和行之间存在一种一对一的关系:一个索引条目就指向一行。而对于位图索引,一个索引条目则使用一个位图同时指向多行。位图索引适用于高度重复而且通常只读的数据(高度重复是指相对于表中的总行数,数据只有很少的几个不同值)。在一个OLTP数据库中,由于存在并发性相关的问题,所以不能考虑使用位图索引。
? 位图联结索引(bitmap join index):这为索引结构(而不是表)中的数据提供了一种逆规范化的方法。例如,请考虑简单的EMP和DEPT表。有人可能会问这样一个问题:“多少人在位于波士顿的部门工作?“EMP有一个指向DEPT的外键,要想统计LOC值为Boston的部门中的员工人数,通常必须完成表联结,将LOC列联结至EMP记录来回答这个问题。通过使用位图联结索引,则可以在EMP表上对LOC列建立索引。
? 基于函数的索引(function-based index):这些就是B*树索引或位图索引,它将一个函数计算得到的结果存储在行的列中,而不是存储列数据本身。可以把基于函数的索引看作一个虚拟列(或派生列)上的索引,换句话说,这个列并不物理存储在表中。
? 应用域索引(application domain index):应用域索引是你自己构建和存储的索引,可能存储在Oracle中,也可能在Oracle之外。你要告诉优化器索引的选择性如何,以及执行的开销有多大,优化器则会根据你提供的信息来决定是否使用你的索引。Oracle文本索引就是应用域索引的一个例子;你也可以使用构建Oracle文本索引所用的工具来建立自己的索引。需要指出,这里创建的“索引“不需要使用传统的索引结构。例如,Oracle文本索引就使用了一组表来实现其索引概念。

B*树最底层的块称为叶子节点(leaf node)或叶子块(leaf block),其中分别包含各个索引键以及一个rowid(指向所索引的行)。叶子节点之上的内部块称为分支块(branch block)。这些节点用于在结构中实现导航。

B*树索引中不存在非惟一(nonunique)条目。在一个非惟一索引中,Oracle会把rowid作为一个额外的列(有一个长度字节)追加到键上,使得键惟一。在一个惟一索引中,根据你定义的惟一性,Oracle不会再向索引键增加rowid。在非惟一索引中,你会发现,数据会首先按索引键值排序(依索引键的顺序)。然后按rowid升序排序。而在惟一索引中,数据只按索引键排序。

B*树的特点之一是:所有叶子块都应该在树的同一层上。这一层也称为索引的高度(height)。这说明所有从索引的根块到叶子块的遍历都会访问同样数目的块。
注意 Oracle在表示从索引根块到叶子块遍历所涉及的块数时用了两个含义稍有不同的术语。第一个是高度(HEIGHT),这是指从根块到叶子块遍历所需的块数。使用ANALYZE INDEX  VALIDATE STRUCTURE命令分析索引后,可以从INDEX_STATS视图找到这个高度(HEIGHT)值。另一个术语是BLEVEL,这是指分支层数,与HEIGHT相差1(BLEVEL不把叶子块层算在内)。收集统计信息后,可以在诸如USER_INDEXES之类的常规字典表中找到BLEVEL值。

压缩键索引(compressed key index)的基本概念是,每个键条目分解为两个部分:“前缀”和“后缀”。前缀建立在串联索引(concatenated index)的前几列上,这些列有许多重复的值。后缀则在索引键的后几列上,这是前缀所在索引条目中的惟一部分(即有区别的部分)。每删除一个重复的前缀,能节省N个副本的空间,但是作为压缩机制的一部分,这会在叶子块上增加4字节的开销。

实现B*树索引的目的是为了减少“右侧”索引中对索引叶子块的竞争,比如在一个Oracle RAC环境中,某些列用一个序列值或时间戳填充,这些列上建立的索引就属于“右侧”(right-hand-side)索引。

反向键主要用于缓解索引右侧的缓冲区忙等待。

什么情况下应该使用B*树索引?下面给出两种做法,这两种做法同等有效:
? 仅当要通过索引访问表中很少的一部分行(只占一个很小的百分比)时,才使用B*树在列上建立索引。
? 如果要处理表中的多行,而且可以使用索引而不用表,就可以使用一个B*树索引。

我们要特别查看USER_INDEXES视图中的CLUSTERING_FACTOR列。Oracle reference手册指出了这个列有以下含义:
根据索引的值指示表中行的有序程度:
? 如果这个值与块数接近,则说明表相当有序,得到了很好的组织,在这种情况下,同一个叶子块中的索引条目可能指向同一个数据块上的行。
? 如果这个值与行数接近,表的次序可能就是非常随机的。在这种情况下,同一个叶子块上的索引条目不太可能指向同一个数据块上的行。
可以把聚簇因子(clustering factor)看作是通过索引读取整个表时对表执行的逻辑I/O次数。也就是说,CLUSTERING_FACTOR指示了表相对于索引本身的有序程度。

当Oracle对索引结构执行区间扫描时,如果它发现索引中的下一行于前一行在同一个数据库块上,就不会再执行另一个I/O从缓冲区缓存中获得表块。它已经有表块的一个句柄,只需直接使用就可以了。不过,如果下一行不在同一个块上,就会释放当前的这个块,而执行另一个I/O从缓冲区缓存获取要处理的下一个块。

什么时候建立索引,在哪些列上建立索引,你的设计中必须注意这些问题。索引并不一定就意味着更快的访问;实际上你会发现,在许多情况下,如果Oracle使 用索引,反而会使性能下降。这实际上两个因素的一个函数,其中一个因素是通过索引需要访问表中多少数据(占多大的百分比),另一个因素是数据如何布局。如 果能完全使用索引“回答问题“(而不用表),那么访问大量的行(占很大的百分比)就是有意义的,因为这样可以避免读表所带来的额外的分散I/O。如果使用索引来访问表,可能就要确保只处理整个表中的很少一部分(只占很小的百分比)。

位图索引是这样一种结构,其中用一个索引键条目存储指向多行的指针;这与B*树结构不同,在B*树结构中,索引键和表中的行存在着对应关系。在位图索引中,可能只有很少的索引条目,每个索引条目指向多行。而在传统的B*树中,一个索引条目就指向一行。

位图索引在读密集的环境中能很好地工作,但是对于写密集的环境则极不适用。原因在于,一个位图索引键条目指向多行。如果一个会话修改了所索引的数据,那么在大多数情况下,这个索引条目指向的所有行都会被锁定。Oracle无 法锁定一个位图索引条目中的单独一位;而是会锁定这个位图索引条目。倘若其他修改也需要更新同样的这个位图索引条目,就会被“关在门外“。这样将大大影响 并发性,因为每个更新都有可能锁定数百行,不允许并发地更新它们的位图列。

创建index的语句:
create bitmap index emp_bm_idx
 on emp( d.dname )
 from emp e, dept d
 where e.deptno = d.deptno
位图联结索引确实有一个先决条件。联结条件必须联结到另一个表中的主键或惟一键。

在创建函数中,我们使用了一个新的关键字DETERMINISTIC。这就声明了:这个函数在给定相同的输入时,总会返回完全相同的输出。要在一个用户编写的函数上创建索引,这个关键字是必要的。我们必须告诉Oracle这个函数是确定性的(DETERMINISTIC),而且在给定相同输入的情况下总会返回一致的结果。通过这个关键字,就是在告诉Oracle:可以相信这个函数,给定相同的输入,不论做多少次调用,它肯定能返回相同的值。如果不是这样,通过索引访问数据时就会得到与全表扫描不同的答案。这种确定性设置表明在有些函数上是不能建立索引的,例如,我们无法在函数DBMS_RANDOM.RANDOM上创建索引,因为这是一个随机数生成器。函数DBMS_RANDOM.RANDOM的结果不是确定性的;给定相同的输入,我们会得到随机的输出。另一方面,第一个例子中所用的内置SQL函数UPPER则是确定性的,所有可以在列的UPPER值上创建一个索引。

要利用基于函数的索引,还有一个有用的技术,这就是使用这种索引来保证某种复杂的约束。例如,假设有一个带版本信息的表,如项目表。项目有两种状态:要么为ACTIVE,要么为INACTIVE。需要保证以下规则:“活动的项目必须有一个惟一名;而不活动的项目无此要求。”
由于可以在函数上创建索引,而且B*树索引中对于完全为NULL的行没有相应的条目,另外我们可以创建一个UNIQUE索引,基于这几点,可以很容易做到:
Create unique index active_projects_must_be_unique
On projects ( case when status = 'ACTIVE' then name end );
这就行了。状态(status)列是ACTIVE时,NAME列将建立惟一的索引。

倘若优化器没有使用你的基于函数的索引,而且你对此无法做出解释,不知道为什么没有使用你的函数,在这种情况下,就可以检查USER_IND_EXPRESSIONS视图,验证使用的函数是否正确。

对于基于函数的索引,我注意到这样一个奇怪的现像,如果你要在内置函数TO_DATE上创建一个索引,某些情况下并不能成功创建,例如:
create index t_idx on t( to_date(year,'YYYY') );
此时会有ORA-01743的警告。为何会产生此种现象?问题只是在于我们使用的格式:YYYY。给定完全相同的输入,这种格式可能返回不同的答案,这取决于调用的函数时输入的月份。YYYY格式会返回5月1日,在6月它会返回6月1日,以此类推。这就说明,如果用到YYYY,TO_DATE就不是确定性的!这这是无法创建索引的原因:只有在创建一行(或插入/更新一行)的多月它能正确工作。所以,这个错误确实归根于用户环境,其中包含当前日期本身。要在一行基于函数的索引中使用TO_DATE,必须使用一种无歧义的确定性日期格式,而不论当前是哪一天。

不使用索引的主要原因:
? 假设有一个表T,在T(X,Y)上有一个索引;如果查询是SELECT X, Y FROM T WHERE Y = 5,优化器就会注意到,它不必全面扫描表来得到X或Y(X和Y都在索引中),对索引本身做一个快速的全面扫描会更合适,因为这个索引一般比底层表小得多。还要注意,仅CBO能使用这个访问路径。
另一种情况下CBO也会使用T(x,y)上的索引,这就是索引跳跃式扫描。当且仅当索引的最前列(在上一个例子中,最前列就是Y)只有很少的几个不同值,而且优化器了解这一点,跳跃式扫描(skip scan)就能很好地发挥作用。

? 我们在使用一个SELECT COUNT(*) FROM T查询(或类似的查询),而且在表T上有一个B*树索引。不过,优化器并不是统计索引条目,而是在全面扫描这个表(尽管索引比表要小)。在这种情况下,索引可能建立在一些允许有null值的列上。由于对于索引键完全为null的行不会建立相应的索引条目,所以索引中的行数可能并不是表中的行数。这里优化器的选择是对的,如若不然,倘若它使用索引来统计行数,则可能会得到错误的答案。

? 我们已经对一个字符创建了索引。这个列只包含数值数据。如果所用以下语句来查询:
select * from t where indexed_column = 5
注意查询中的数字5是常数5(而不是一个字符串),此时就没有使用INDEX_COLUMN上的索引。这是因为,前面的查询等价于一些查询:
select * from t where to_number(indexed_column) = 5;
我们对这个列隐式地应用了一个函数

? 关于CASE的警告。基于函数的索引中引用的函数会以某种方式被重写,以至于索引无法被透明地使用,例如,前面的CASE语句:
case when x = 42 then 11.end;通过查看视图USER_IND_EXPRESS来看看Oracle会将它重写为:CASE "X" WHEN 42 THEN 11.END
因此在运行where (case when x = 42 then 11.end ) = 1;的条件时,这个函数与我们创建的那个函数不再匹配,所以查询无法使用此函数。
在以前的版本中,对此的解决办法有以下几种:
1. 使用DECODE而不是CASE,因为DECODE不会被重写,即所谓的“所见即所得”
2. 使用最先搜索到的CASE语法(预计到可能会发生的优化)


索引中的空间可以得到重用;如果我用值1~500,000加载一个表,然后隔行删除表记录(删除所有偶数行),那么这个索引中那一列上就会有250,000个”洞“。只有当我重新插入数据,而且这个数据能在有洞的块中放下时,这些空间才会得到重用。Oracle并不打算“收缩”或压缩索引,不过这可以通过ALTER INDEX REBUILD或COALESCE命令强制完成。另一方面,如果我用值1~500,000加载一个表,然后从表中删除值小于或等于250,000的每一行,就会发现从索引中清除的块将放回到索引的freelist中,这个空间完全可以重用。

? 一旦插入了可以重用空间的行,索引块上的空间就会立即重用。
? 索引块为空时,会从索引结构中取出它,并在以后重用。这可能是最早出现这个神话的根源:与表不同,在索引结构中,不能清楚地看出一个块有没有“空闲空间”。在表中,可以看到freelis上的块,即使其中包含有数据。而在索引中,只能在freelist上看到完全为空的块;至少有一个索引条目(但其余都是空闲空间)的块就无法清楚地看到。对于一个索引,只有当块完全为空时才会放在freelist上,这一点与表不同。

使用Oracle DATE类型时,我有以下建议:
? 使用NUMTODSINTERVAL内置函数来增加小时、分钟和秒。
? 加一个简单的数来增加天。
? 使用ADD_MONTHS内置函数来增加月和年。
建议不要使用NUMTOYMINTERVAL函数。其原因与这个函数如何处理月末日期有关。

LOB有以下属性:
? 一个表空间(这个例子中即为USERS)
? ENABLE STORAGE IN ROW作为一个默认属性
? CHUNK 8192
? PCTVERSION 12.
? NOCACHE
? 一个完整的STORAGE子句

实际LOB数据就存储在lobsegment中(确实,LOB数据也有可能存储在表T中)。lobindex用于执行LOB的导航,来找出其中的某些部分。创建一个LOB列时,一般来说,存储在行中的这是一个指针(pointer),或LOB定位器(LOB locator)。我们的应用所获取的就是这个LOB定位器。当请求得到LOB的“12.000~2,000字节”时,将对lobindex使用LOB定位器来找出这些字节存储在哪里,然后再访问lobsegment。

LOB表空间
为什么考虑为LOB数据使用另外一个表空间(而不用表数据所在的表空间)呢?原因与管理和性能有关。从管理的角度看,LOB数据类型表示一种规模很大的信息。如果表有数百万行,而每行有一个很大的LOB,那么LOB就会极为庞大。为LOB数据单独使用一个表空间有利于备份和恢复以及空间管理,单从这一点考虑,将表与LOB数据分离就很有意义。
另一个原因则出于I/O性能的考虑。默认情况下,LOB不在缓冲区缓存中进行缓存。因此,默认情况下,对于每个LOB访问,不论是读还是写,都会带来一个物理I/O(从磁盘直接读,或者向磁盘直接写)。由于每个访问都是一个物理I/O,所以如果你很清楚在实际中(当用户访问时)有些对象会比大多数其他对象经历更多的物理I/O,那么将这些对象分离到它们自己的磁盘上就很有意义。
lobindex和lobsegment总是会在同一个表空间中。不能将lobindex和lobsegment放在不同的表空间中。

IN ROW子句:
这控制了LOB数据是否总与表分开存储(存储在lobsegment中),或是有时可以与表一同存储,而不用单独放在lobsegment中。如果设置了ENABLE STORAGE IN ROW,而不是DISABLE STORAGE IN ROW,小LOB(最多4,000字节)就会像VARCHAR2一样存储在表本身中。只有当LOB超过了4,000字节时,才会“移出”到lobsegment中。默认行为是启用行内存储(ENABLE STORAGE IN ROW)。

CHUNK子句:
LOB存储在块(chunk)中;指向LOB数据的索引会指向各个数据块。块(chunk)是逻辑上连续的一组数据库块(block),这也是LOB的最小分配单元,而通常数据库的最小分配单元是数据库块。CHUNK大小必须是Oracle块大小的整数倍,只有这样才是合法值。

PCTVERSION子句:
这用于控制LOB的读一致性。在前面的几章中,我们已经讨论了读一致性、多版本和undo在其中所起的作用。但LOB实现读一致性的方式有所不同。lobsegment并不使用undo来记录其修改;而是直接在lobsegment本身中维护信息的版本。lobindex会像其他段一样生成undo,但是lobsegment不会。相反,修改一个LOB时,Oracle会分配一个新的CHUNK,并且仍保留原来的CHUNK。如果回滚了事务,对LOB索引所做的修改会回滚,索引将再次指向原来的CHUNK。因此,undo维护会在LOB段本身中执行。修改数据时,原来的数据库保持不动,此外会创建新数据。
我们要考虑这样一个问题:如果不用undo段来存储回滚LOB所需要的信息,而且LOB支持读一致性,那我们怎么避免发生可怕的ORA-01555:snapshot too old错误呢?还有一点同样重要,如何控制这些旧版本占用的空间呢?这正是PCTVERSION起作用的地方。
PCTVERSION控制着用于实现LOB数据版本化的已分配LOB空间的百分比(这些数据库块由某个时间点的LOB所用,并处在lobsegment的HWM以下)。对于许多使用情况来说,默认设置12%就足够了,因为在很多情况下,你只是要INSERT和获取LOB(通常不会执行LOB的更新;LOB往往会插入一次,而获取多次)。因此,不必为LOB版本化预留太多的空间(甚至可以没有)。如果你在使用手动undo管理,并且发生此ERROR,则可以使用如下语句
ALTER TABLE tabname MODIFY LOB (lobname) ( PCTVERSION n );

RETENTION子句:
这个子句与PCTVERSION子句是互斥的,如何数据库中使用自动undo管理,就可以使用这个子句。RETENTION子句并非在lobsegment中保留某个百分比的空间来实现LOB的版本化,而是使用基于时间的机制来保留数据。数据库会设置参数UNDO_RETENTION,指定要把undo信息保留多长时间来保证一致读。在这种情况下,这个参数也适用于LOB数据。
需要注意,不能使用这个子句来指定保留时间;而要从数据库的UNDO_RETENTION设置来继承它。

CACHE子句:
除了NOCACHE,这个选项还可以是CACHE或CACHE READS。这个子句控制了lobsegment数据是否存储在缓冲区缓存中。默认的NOCACHE指示,每个访问都是从磁盘的一个直接读,类似地,每个写/修改都是对大盘的一个直接写。CACHE READS允许缓存从磁盘读的LOB数据,但是LOB数据的写操作必须直接写至磁盘。CACHE则允许读和写时都能缓存LOB数据。
要记住,此时可以充分使用Keep池或回收池。并非在默认缓存中将lobsegment数据与所有“常规”数据一同缓存,可以使用保持池或回收池将其分开缓存。采用这种方式,既能缓存LOB数据,而且不影响系统中现有数据的缓存。

以前ROWID是不可变的。插入一行时,会为之关联一个ROWID(一个地址),而且这个ROWID会一直与该行关联,直到这一行被删除(被物理地从数据库删除)。但是,后来情况发生了变化,因为现在有些操作可能会导致行的ROWID改变,例如:
? 在分区表中更新一行的分区键,使这一行必须从一个分区移至另一个分区。
? 使用FLASHBACK TABLE命令将一个数据库表恢复到以前的每个时间点。
? 执行MOVE操作以及许多分区操作,如分解或合并分区。
? 使用ALTER TABLE SHRINK SPACE命令执行段收缩。

通过使用散列分区,我们让Oracle随机地(很可能均匀地)将数据分布到多个分区上。我们无法控制数据要分布到哪个分区上;Oracle会根据生成的散列键值来确定。

关于分区和管理,还有一个因素需要考虑,这就是在维护数据仓库和归档中使用数据“滑动窗口”。在许多情况下,需要保证数据在最后N个时间单位内一直在线。
例如,假设需要保证最后12个月或最后5年的数据在线。如果没有分区,这通常是一个大规模的INSERT,其后是一个大规模的DELETE。为此有相对多的DML,并且会生成大量的redo和undo。如果进行了分区,则只需做下面的工作:
(1) 用新的月(或年,或者是其他)数据加载一个单独的表。
(2) 对这个表充分建立索引(这一步甚至可以在另一个实例中完成,然后传送到这个数据库中)。
(3) 将这个新加载(并建立了索引)的表附加到分区表的最后,这里使用一个快速DDL命令:ALTER TABLE EXCHANGE PARTITION。
(4) 从分区表另一端将最旧的分区去掉。
如果希望在一个表上执行PDML,而且这个表的一个LOB列上有一个位图索引,要并行执行操作就必须对这个表分区;另外并行度就限制为分区数。

目前Oracle中有4种对表分区的方法:
? 区间分区:可以指定应当存储在一起的数据区间。例如,时间戳在Jan-2005内的所有记录都存储在分区1中,时间戳在Feb-2005内的所有记录都存储在分区2中,依此类推。这可能是Oracle中最常用的分区机制。
? 散列分区:我们在这一章一个例子中就已经看到了散列分区。这是指在一个列(或多个列)上应用一个散列函数,行会按这个散列值放在某个分区中。
? 列表分区:指定一个离散值集,来确定应当存储在一起的数据。例如,可以指定STATUS列值在(’A’,’M’,’Z’)中的行放在分区1中,STATUS值在(‘D’,’P’,’Q’)中的行放在分区2中,依此类推。
? 组合分区:这是区间分区和散列分区的一种组合,或者是区间分区与列表分区的组合。通过组合分区,你可以先对某些数据应用区间分区,再在区间中根据散列或列表来选择最后的分区。

散列分区数要使用2的幂,这一点非常重要。

关于DEFAULT的使用,有一点要注意:一旦列表分区表有一个DEFAULT分区,就不能再向这个表中增加更多的分区了:此时必须删除DEFAULT分区,如何增加分区,再加回DEFAULT分区。

使用组合分区时,并没有分区段,而只有子分区段。分区本身并没有段(这就类似于分区表没有段)。数据物理的存储在子分区段上,分区成为一个逻辑容器,或者是一个指向实际子分区的容器。

在前面所述的各种分区机制中,如果用于确定分区的列有修改会发生什么。需要考虑两种情况:
? 修改不会导致使用一个不同的分区;行仍属于原来的分区。这在所有情况下都得到支持。
? 修改会导致行跨分区移动。只有当表启用了行移动时才支持这种情况;否则,会产生一个错误。

索引与表类似,也可以分区。对索引进行分区有两种可能的方法:
? 随表对索引完成相应的分区:这也称为局部分区索引(locally pertitioned index)。每个表分区都有一个索引分区,而且只索引该表分区。一个给定索引分区中的所有条目都指向一个表分区,表分区中的所有行都表示在一个索引分区中。
? 按区间对索引分区:这也称为全局分区索引(globally partitioned index)。在此,索引按区间分区(或者在Oracle 10g中该可以按散列分区),一个索引分区可能指向任何(和所有)表分区。
由于全局索引只按区间或散列分区,如果希望有一个列表或组合分区索引,就必须使用局部索引。局部索引会使用底层表相同的机制分区。

Oracle划分了以下两类局部索引:
? 局部前缀索引(local prefixed index):在这些索引中,分区键在索引定义的前几列上。例如,一个表在名为LOAD_DATE的列上进行区间分区,该表上的局部前缀索引就是LOAD_DATE作为其索引列列表中的第一列。
? 局部非前缀索引(local nonprefixed index):这些索引不以分区键作为其列列表的前几列。索引可能包含分区键列,也可能不包含。要使用非前缀索引,必须使用一个允许分区消除的查询。

如果你有多个如前所列的查询(可以得益于非前缀索引),就应该考虑使用一 个非前缀索引。重点是,要尽可能保证查询包含的谓词允许索引分区消除。使用前缀局部索引可以保证这一点,使用非前缀索引则不能保证。还要考虑如何使用索 引。如果将索引用作查询计划中的第一步,那么这两种类型的索引没有多少差别。

Oracle只保证索引分区内部的惟一性,而不能跨分区。这说明什么呢?例如,这意味着不能一方面在一个TIMESTAMP字段上执行区间分区,而另一方面在ID上有一个主键(使用一个局部分区索引来保证)。Oracle会利用全局索引来保证惟一性。

为什么局部分区索引不能保证惟一性(除非分区键是约束的一部分),原因有两方面。首先,如果Oracle允 许如此,就会丧失分区的大多数好处。可用性和可扩缩性都会丧失殆尽,因为对于任何插入和更新,总是要求所有分区都一定可用,而且要扫描每一个分区。你的分 区越多,数据就会变得越不可用。另外,分区越多,要扫描的索引分区就越多,分区也会变得越发不可扩缩。这样做不仅不能提供可用性和可扩缩性,相反,实际上 反倒会削弱可用性和可扩缩性。
另外,倘若局部分区索引能保证惟一性,Oracle就必须在事务级对这个表的插入和更新有效地串行化。这是因为,如果向PART_1增加ID=1,Oracle就必须以某种方式防止其他人向PART_2增加ID=1。对此惟一的做法是防止别人修改索引分区PART_2,因为无法通过对这个分区中的内容“锁定”来做到(找不出什么可以锁定)。

对系统应用分区来“提供性能”之前,先要确保自己真正了解系统需要什么。如果系统目前是CPU密集的(占用大量CPU时间),但是CPU的使用并不是因为竞争和闩等待,那么引入分区并不能使问题好转,而只会让情况变得更糟糕!

为处理审计跟踪信息可以有以下表空间:
? 一个当前在线的读写表空间,它会像系统中每一个其他的正常表空间一样得到备份。这个表空间中的审计跟踪信息不会被压缩,我们只是向其中插入信息。
? 一个只读表空间,其中包含“当前这一年”的审计跟踪信息分区,在此采用一种压缩格式。在每个月的月初,置这个表空间为可读写,向这个表空间中移入上个月的审计信息,并进行压缩,再使之成为只读表空间,并完成备份。
? 用于去年、前年等的一系列表空间。这些都是只读表空间,甚至可以放在很慢的廉价存储介质上。如果出现介质故障,我们只需要从备份恢复。有时可以随机地从备份集中选择每一年的信息,确保这些信息是可恢复的(有时磁带会出故障)。

并行执行本质上是一个不可扩缩的解决方案,设计为允许单个用户或每个特定SQL语句占用数据库的所有资源。如果某个特性允许一个人使用所有可用的资源,倘若再允许两个人使用这个特性,就会遇到明显的竞争问题。

在应用并行执行之前,需要保证以下两点成立:
? 必须有一个非常大的任务,如对50GB数据进行全面扫描。
? 必须有足够的可用资源。在并行全面扫描50GB数据之前,你要确保有足够的空闲CPU(以容纳并行进程),还要有足够的I/O通道。50GB数据可能分布在多个物理磁盘上(而不只是一个物理磁盘),以允许多个并发读请求能同时发生,从磁盘到计算机应当有足够多的I/O通道,以便能并行地从磁盘获取数据,等等。

alter table big_table parallel 4;
运行上面脚本后,当我们创建这个表的执行计划时使用并行度4;
也可以不在parallel后加具体的数值,这就代表oracle会考虑并行执行,但是会根据当前的系统工作负载和查询本身来确定适当的并行度。也就是说,并行度会随着系统上工作负载的增减而变化。如果有充足的空闲资 源,并行度会上升;如果可用资源有限,并行度则会下降。这样就不会为机器强加一个固定的并行度。利用这种方法,允许Oracle动态地增加或减少查询所需的并发资源量。

查询协调器SID正在控制哪些并行查询资源:
select sid, qcsid, server#, degree
from v$px_session
 where qcsid = sys_context('userenv', 'sid');

类似于Oracle执行的分布式查询,PDML操作采用同样的方式执行,即每个并行执行服务器相当于一个单独数据库实例中的一个进程。表的每一部分(每一片)由一个单独的线程利用其自己的独立事务来修改(相应地,这个线程可能有自己的undo段)。这些事务都结束后,会执行一个相当于快速2PC的过程来提交这些单独的独立事务。
PDML有别于并行查询;除非显式地请求PDML,否则不能执行PDML。
alter session enable parallel dml;

PDML采用的一种伪分布式的实现,因此存在一些限制:
? PDML操作期间不支持触发器。在我看来,这是一个很合理的限制,因为触发器可能会向更新增加大量开销,而你使用PDML的本来目的是为了更快一些,这两方面是矛盾的,不能放在一起。
? PDML期间,不支持某些声明方式的引用完整性约束,因为表中的每一片(部分)会在单独的会话中作为单独的事务进行修改。例如,PDML操作不支持自引用完整性。如果真的支持自引用完整性,可能会出现死锁和其他锁定问题。
? 在提交或回滚之前,不能访问用PDML修改的表。
? PDML不支持高级复制(因为复制特性的实现要基于触发器)。
? 不支持延迟约束(也就是说,采用延迟模式的约束)。
? 如果表是分区的,PDML只可能在有位图索引或LOB列的表上执行,而且并行度取决于分区数。在这种情况下,无法在分区内并行执行一个操作,因为每个分区只有一个并行执行服务器来处理。
? 执行PDML时不支持分布式事务。
? PDML不支持聚簇表。

并行DDL依赖于直接路径操作。也就是说,数据不传递到缓冲区缓存以便以后写出;而是由一个操作(如CREATE TABLE AS SELECT)来创建新的区段,并直接写入这些区段,数据直接从查询写到磁盘(放在这些新分配的区段中)。每个并行执行服务器执行自己的部分CREATE TABLE AS SELECT工作,并且都会写至自己的区段。INSERT /*+ APPEND */(直接路径插入)会在一个段的HWM“之上“写,每个并行执行服务器再写至其自己的一组区段,而不会与其他并行执行服务器共享。因此,如果执行一个并行CREATE TABLE AS SELECT,并使用4个并行执行服务器来创建表,就至少有4个分区,可能还会更多。每个并行执行服务器会分配其自己的区段,向其写入,等填满时,再分配另一个新的区段,并行执行服务器不会使用由其他并行执行服务器创建的区段。

假设你想加载1,010MB的数据(大约1GB),而且正在使用一个有100MB区段的表空间,你决定使用10个并行执行服务器来加载这个数据。每个并行执行服务器先分配其自己的100MB区段(总共会有10个100MB的区段),并在其中填入数据。由于每个并行执行服务器都要加载101MB的数据,所以它会填满第一个区段,然后再继续分配另一个100MB的区段,但实际上只会使用这个区段中1MB的空间。现在就有了20区段,其中10个是满的,另外10个则不同,这10个区段中都各有1MB的数据,因此,总共会有990MB的空间是”已分配但未使用的“。下一次加载是可以使用这个空间,但是对现在来说,你就有了990MB的死空间。此时区段截断(extend trimming)就能派上用场了。Oracle会试图取每个并行执行服务器的最后一个区段,并将其”截断为“可能的最小大小。

本地管理表空间有两种类型:UNIFORM. SIZE 和AUTOALLOCATE,UNIFORM. SIZE是指表空间中的每个区段大小总是完全相同;AUTOALLOCATE则表示Oracle会使用一种内部算法来确定每个区段应该是多大。
UNIFORM. SIZE方法完全排除了区段截断。如果使用UNIFORM. SIZE,Oracle就不能执行区段截断。所有区段都只能有惟一一种大小,不能有任何区段小于(或大于)这个大小。
AUTOALLOCATE方法使用一些特定大小的区段,而且能使用不同大小的空间。也就是说,利用这种算法,一段时间后将允许使用表空间中的所有空闲空间。

如果你是这台机器的惟一用户(所有资源都可用),又根据CPU数(4)和PARALLEL_THREADS_PER_CPU参数设置(默认为2),Oracle会将并行度默认为8。

有两种操作模式:
? 传统路径:(conventional path):SQLLDR会利用SQL插入为我们加载数据。
? 直接路径(direct path):采用这种模式,SQLLDR不使用SQL;而是直接格式化数据库块。

在运行SQLLOAD时如果表非空,就会收到一个错误消息:
SQLLDR-601: For INSERT option, table must be empty. Error on table DEPT
这是因为,这个控制文件中几乎所有选项都取默认值,而默认的加载选项是INSERT(而不是APPEND、TRUNCATE或REPLACE)。要执行INSERT, SQLLDR就认为表为空。如果想向DEPT表中增加记录,可以指定加载选项为APPEND;或者,为了替换DEPT表中的数据,可以使用REPLACE或TRUNCATE。REPLACE使用一种传统DELETE语句;因此,如果要加载的表中已经包含许多记录,这个操作可能执行得很慢。TRUNCATE则不同,它使用TRUNCATE SQL命令,通常会更快地执行,因为它不必物理地删除每一行。




































来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/13129975/viewspace-659537/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/13129975/viewspace-659537/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
探索全栈前端技术的魅力:HTML+CSS+JS+JQ+Bootstrap网站源码深度解析 在这个数字化时代,构建一个既美观又功能强大的网站成为了许多开发者和企业追逐的目标。本份资源精心汇集了一套完整网站源码,融合了HTML的骨架搭建、CSS的视觉美化、JavaScript的交互逻辑、jQuery的高效操作以及Bootstrap的响应式设计,全方位揭秘了现代网页开发的精髓。 HTML,作为网页的基础,它构建了信息的框架;CSS则赋予网页生动的外观,让设计创意跃然屏上;JavaScript的加入,使网站拥有了灵动的交互体验;jQuery,作为JavaScript的强力辅助,简化了DOM操作与事件处理,让编码更为高效;而Bootstrap的融入,则确保了网站在不同设备上的完美呈现,响应式设计让访问无界限。 通过这份源码,你将: 学习如何高效组织HTML结构,提升页面加载速度与SEO友好度; 掌握CSS高级技巧,如Flexbox与Grid布局,打造适应各种屏幕的视觉盛宴; 理解JavaScript核心概念,动手实现动画、表单验证等动态效果; 利用jQuery插件快速增强用户体验,实现滑动效果、Ajax请求等; 深入Bootstrap框架,掌握移动优先的开发策略,响应式设计信手拈来。 无论是前端开发新手渴望系统学习,还是资深开发者寻求灵感与实用技巧,这份资源都是不可多得的宝藏。立即深入了解,开启你的全栈前端探索之旅,让每一个网页都成为技术与艺术的完美融合!
探索全栈前端技术的魅力:HTML+CSS+JS+JQ+Bootstrap网站源码深度解析 在这个数字化时代,构建一个既美观又功能强大的网站成为了许多开发者和企业追逐的目标。本份资源精心汇集了一套完整网站源码,融合了HTML的骨架搭建、CSS的视觉美化、JavaScript的交互逻辑、jQuery的高效操作以及Bootstrap的响应式设计,全方位揭秘了现代网页开发的精髓。 HTML,作为网页的基础,它构建了信息的框架;CSS则赋予网页生动的外观,让设计创意跃然屏上;JavaScript的加入,使网站拥有了灵动的交互体验;jQuery,作为JavaScript的强力辅助,简化了DOM操作与事件处理,让编码更为高效;而Bootstrap的融入,则确保了网站在不同设备上的完美呈现,响应式设计让访问无界限。 通过这份源码,你将: 学习如何高效组织HTML结构,提升页面加载速度与SEO友好度; 掌握CSS高级技巧,如Flexbox与Grid布局,打造适应各种屏幕的视觉盛宴; 理解JavaScript核心概念,动手实现动画、表单验证等动态效果; 利用jQuery插件快速增强用户体验,实现滑动效果、Ajax请求等; 深入Bootstrap框架,掌握移动优先的开发策略,响应式设计信手拈来。 无论是前端开发新手渴望系统学习,还是资深开发者寻求灵感与实用技巧,这份资源都是不可多得的宝藏。立即深入了解,开启你的全栈前端探索之旅,让每一个网页都成为技术与艺术的完美融合!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值