Oracle中的段(segment)是占用磁盘上存储空间的一个对象。尽管有多种类型,不过最常见的段类型如下:
q 聚簇(cluster):这种段类型能存储表。有两种类型的聚簇:B*树聚簇和散列聚簇。聚簇通常用于存储多个表上的相关数据,将其“预联结”存储到同一个数据库块上;还可以用于存储一个表的相关信息。“聚簇”这个词是指这个段能把相关的信息物理的聚在一起。
q 表(table):表段保存一个数据库表的数据,这可能是最常用的段类型,通常与索引段联合使用。
q 表 分区(table partition)或子分区(subpartition):这种段类型用于分区,与表段很相似。分区表由一个或多个分区段(table partition segment)组成,组合分区表则由一个或多
个表子分区段(table subpartition segment)组成。
q 索引(index):这种段类型可以保存索引结构。
q 索引分区(index partition):类似与表分区,这种段类型包含一个索引的某个片。分区索引由一个或多个索引分区段(index partition segment)组成。
q 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取一个特 殊的名字!)。
q 嵌套表(nested table):这是为嵌套表指定的段类型,它是主/明细关系中一种特殊类型的“子”表,这种关系随后将详细讨论。
q 回滚段(rollback)和Type2 undo段:undo数据就存储在这里。回滚段是DBA手动创建的段。Type2 undo段由Oracle自动创建和管理。
段空间管理
从Oracle 9i开始,管理段空间有两种方法:
q 手 动段空间管理(Manual Segment Space Management):由你设置FREELISTS、FREELIST GROUPS、PCTUSED和其他参数来控制如何分配、使用和重用段中的空间。在这一章中我会把这种空间管理方法称为MSSM,但是要记住,这是一个我 自造的缩写,Oracle文档中没有这个缩写。
q 自动段空间管理(Automatic Segment Space Management, ASSM):你只需控制与空间使用相关的一个参数:PCTFREE。创建段时也可以接受其他参数,但是这些参数将被忽略。
高水位线
HWM很重要, 因为Oracle在全面扫描段时会扫描HWM之下的所有块, 即使其中不包含任何数据。这会影响全面扫描的性能,特别是当HWM之下的大多数块都为空时。要查看这种情况,只需创建一个有1,000,000行的表(或者创建其他有大量行的表),然后对这个表执行一个SELECT COUNT(*)。 下面再删除(DELETE)这个表中的每一行,你会发现尽管SELECT COUNT(*)统计出0行,但是它与统计出1,000,000所花的时间一样长(如果需要完成块清除,时间可能还会更长:有关内容请参加9.5.5 节)。这是因为Oracle在忙于读取HWM之下的所有块, 查看其中是否包含数据。 如果对这个表使用TRUNCATE而不是删除其中的每一行,你可以比较 一下结果有什么不同。TRUNCATE会把表的HWM重置回“0”,还会截除表上的相关索引。由于以上原因,如果你打算删除表中的所有行,就应该选择使用 TRUNCATE
(如果可以使用的话)
临时表(Temporary table)用于保存事务或会话期间的中间结果集。临时表中保存的数据只对当前会话可见,所有会话都看不到其他会话的数据;即使当前会话已经提交 (COMMIT)了数据,别的会话也看不到它的数据。对于临时表,不存在多用户并发问题,因为一个会话不会因为使用一个临时表而阻塞另一个会话。即使我们 “锁住”了临时表,也不会妨碍其他会话使用它们自己的临时表。
临时表比常规表生成的redo少得多。不过,由于临时表必须为其中包含 的数据生成undo信息, 所以也会生成一定的redo。 UPDATE和DELETE会生成最多的undo; INSERT和SELECT生成的undo最 少。
临 时表会从当前登录用户的临时表空间分配存储空间,或者如果从一个定义者权限(definer right)过程访问临时表,就会使用该过程所有者的临时表空间。全局临时表实际上是表本身的一个模板。创建临时表的动作不涉及存储空间分配;不会为此分 配初始(INITIAL)区段,这与常规表有所不同。对于临时表,运行时当一个会话第一次在临时表中放入数据时,才会为该会话创建一个临时段。由于每个会 话会得到其自己的临时段(而不是一个现有段的一个区段),每个用户可能在不同的表空间为其临时表分配空间。
Oracle的 临时表与其他关系数据库中的临时表类似,这样区别只是:Oracle的临时表是“静态”定义的。每个数据库只创建一次临时表,而不是为数据库中的每个存储过程都创建一次。在Oracle中,临时表一定存在,它们作为对象放在数据字典中,但是在会话向临时表中放入数据之前,临时表看上去总是空。由于临时表是静态定义的,所以你能创建引用临时表的视图,还可以创建存储过程使用静态SQL来引用临时表,等等。临时表可以是基于会话的(临时表中的数据可以跨提交存在,即提交之前仍然存在,但是断开连接后再连接后再连接时数据就没有了),也可以是基于事务的(提交之后数据就消失)。下面
这个例子显示了这两种不同的临时表
create global temporary table temp_table_session
2 on commit preserve rows
3 as
4 select * from scott.emp where 1=0
这是创建会话临时表
ON COMMIT DELETE ROWS子句使得这是一个基于事务的临时表
如果你曾在SQL Server和/或Sybase中用过临时表,现在所要考虑的主要问题是:不应该执行SELECT X, Y, Z INTO #TEMP FROM SOME_TABLE来动态创建和填充一个临时表,而应该:将所有全局临时表只创建一次,作为应用安装的一部分,就像是创建永久表一样。在你的过程中,只需执行INSERT INTO TEMP(X, Y, Z) SELECT X, Y, Z FROM SOME_TABLE。
归根结底,这里的目标是:不要在运行时在你的存储过程中创建表。这不是Oracle中使用临时表的正确做法。DDL是一种代价昂贵的操作:你要全力避免在运行时执行这种操作。一个应用的临时表应该在应用安装期间创建,绝对不要在运行时创建。
临时表可以有永久表的许多属性。它们可以有触发器、检查约束、索引等。但永久表的某些特性在临时表中并不支持,这包括:
q 不能有引用完整性约束。临时表不能作为外键的目标,也不能在临时表中定义外键。
q 不能有NESTED TABLE类型的列。 在Oracle 9i及以前版本中, VARRAY类型的列也不允许;
不过Oracle 10g中去掉了这个限制。
q 不能是IOT。
q 不能在任何类型的聚簇中。
q 不能分区。
q 不能通过ANALYZE表命令生成统计信息。
在 所有数据库中,临时表的缺点之一是优化器不能正常地得到临时表的真实统计。使用基于代价的优化器(cost-based optimizer,CBO)时,有效的统计对于优化器的成败至关重要。如果没有统计信息,优化器就只能对数据的分布、数据量以及索引的选择性作出猜测。 如果这些猜测是错的,为查询生成的查询计划(大量使用临时表)可能就不是最优的。在许多情况下,正确的解决方案是根本不使用临时表,而是使用一个 INLINE VIEW(要看INLINE VIEW的例子,可以查看前面运行的SELECT,它就有两个内联视图)。采用这种方式,Oracle可以访问一个表的所有相关统计信息,而且得出一个最 优计划
我经常发现,人们之所以使用临时表,是因为他们在其他数据库中了解到一个查询中联结太多的表是一件“不好的事情”。但在Oracle开发中,必须把这个知识忘掉。不要想着你比优化器要聪明,来一个查询分解成3个或4个查询,将其子结果存储在临时表中,然后再合并这些临时表;正确的做法是应该编写一个查 询,直接回答最初的问题。在一个查询中引用多个表是可以的;Oracle中在这个方面不需要临时表的帮助
临时表小结
如 果应用中需要临时存储一个行集由其他表处理(可能对应一个会话,也可能对应一个事务),临时表就很有用。不要把临时表作为一个分解大查询的方法,即拿到一 个大查询,把它“分解”为几个较小的结果集,然后再把这些结果集合并在一起(这看来是其他数据库中最常见的临时表用法)。实际上,你会发现,在几乎所有的 情况下。Oracle中如果将一个查询分解为较小的临时表查询,与原来的一个查询相比,只会执行得更慢。我就经常看到人们这样做,如果有可能把对临时表的 一系列INSERT重写为一个大查询(SELECT),所得到的单个查询会比原来的多步过程快得多。
临 时表会生成少量的redo,但是确实还是会生成redo,而且没有办法避免。这些redo是为回滚数据生成的,而且在最典型的情况下,可以忽略不计。如果 只是对临时表执行INSERT和SELETE,生成的redo量几乎注意不到。只有对临时表执行大量DELETE和UPDATE时,才会看到生成大量的 redo。 如果精心设计,可以在临时表上生成CBO使用的统计信息;不过,可以使用DBMS_STATS包对临时表上的统计给出更好的猜测,或者由优化器使用动态采样在硬解析时动态收集。
q 聚簇(cluster):这种段类型能存储表。有两种类型的聚簇:B*树聚簇和散列聚簇。聚簇通常用于存储多个表上的相关数据,将其“预联结”存储到同一个数据库块上;还可以用于存储一个表的相关信息。“聚簇”这个词是指这个段能把相关的信息物理的聚在一起。
q 表(table):表段保存一个数据库表的数据,这可能是最常用的段类型,通常与索引段联合使用。
q 表 分区(table partition)或子分区(subpartition):这种段类型用于分区,与表段很相似。分区表由一个或多个分区段(table partition segment)组成,组合分区表则由一个或多
个表子分区段(table subpartition segment)组成。
q 索引(index):这种段类型可以保存索引结构。
q 索引分区(index partition):类似与表分区,这种段类型包含一个索引的某个片。分区索引由一个或多个索引分区段(index partition segment)组成。
q 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取一个特 殊的名字!)。
q 嵌套表(nested table):这是为嵌套表指定的段类型,它是主/明细关系中一种特殊类型的“子”表,这种关系随后将详细讨论。
q 回滚段(rollback)和Type2 undo段:undo数据就存储在这里。回滚段是DBA手动创建的段。Type2 undo段由Oracle自动创建和管理。
段空间管理
从Oracle 9i开始,管理段空间有两种方法:
q 手 动段空间管理(Manual Segment Space Management):由你设置FREELISTS、FREELIST GROUPS、PCTUSED和其他参数来控制如何分配、使用和重用段中的空间。在这一章中我会把这种空间管理方法称为MSSM,但是要记住,这是一个我 自造的缩写,Oracle文档中没有这个缩写。
q 自动段空间管理(Automatic Segment Space Management, ASSM):你只需控制与空间使用相关的一个参数:PCTFREE。创建段时也可以接受其他参数,但是这些参数将被忽略。
高水位线
HWM很重要, 因为Oracle在全面扫描段时会扫描HWM之下的所有块, 即使其中不包含任何数据。这会影响全面扫描的性能,特别是当HWM之下的大多数块都为空时。要查看这种情况,只需创建一个有1,000,000行的表(或者创建其他有大量行的表),然后对这个表执行一个SELECT COUNT(*)。 下面再删除(DELETE)这个表中的每一行,你会发现尽管SELECT COUNT(*)统计出0行,但是它与统计出1,000,000所花的时间一样长(如果需要完成块清除,时间可能还会更长:有关内容请参加9.5.5 节)。这是因为Oracle在忙于读取HWM之下的所有块, 查看其中是否包含数据。 如果对这个表使用TRUNCATE而不是删除其中的每一行,你可以比较 一下结果有什么不同。TRUNCATE会把表的HWM重置回“0”,还会截除表上的相关索引。由于以上原因,如果你打算删除表中的所有行,就应该选择使用 TRUNCATE
(如果可以使用的话)
临时表(Temporary table)用于保存事务或会话期间的中间结果集。临时表中保存的数据只对当前会话可见,所有会话都看不到其他会话的数据;即使当前会话已经提交 (COMMIT)了数据,别的会话也看不到它的数据。对于临时表,不存在多用户并发问题,因为一个会话不会因为使用一个临时表而阻塞另一个会话。即使我们 “锁住”了临时表,也不会妨碍其他会话使用它们自己的临时表。
临时表比常规表生成的redo少得多。不过,由于临时表必须为其中包含 的数据生成undo信息, 所以也会生成一定的redo。 UPDATE和DELETE会生成最多的undo; INSERT和SELECT生成的undo最 少。
临 时表会从当前登录用户的临时表空间分配存储空间,或者如果从一个定义者权限(definer right)过程访问临时表,就会使用该过程所有者的临时表空间。全局临时表实际上是表本身的一个模板。创建临时表的动作不涉及存储空间分配;不会为此分 配初始(INITIAL)区段,这与常规表有所不同。对于临时表,运行时当一个会话第一次在临时表中放入数据时,才会为该会话创建一个临时段。由于每个会 话会得到其自己的临时段(而不是一个现有段的一个区段),每个用户可能在不同的表空间为其临时表分配空间。
Oracle的 临时表与其他关系数据库中的临时表类似,这样区别只是:Oracle的临时表是“静态”定义的。每个数据库只创建一次临时表,而不是为数据库中的每个存储过程都创建一次。在Oracle中,临时表一定存在,它们作为对象放在数据字典中,但是在会话向临时表中放入数据之前,临时表看上去总是空。由于临时表是静态定义的,所以你能创建引用临时表的视图,还可以创建存储过程使用静态SQL来引用临时表,等等。临时表可以是基于会话的(临时表中的数据可以跨提交存在,即提交之前仍然存在,但是断开连接后再连接后再连接时数据就没有了),也可以是基于事务的(提交之后数据就消失)。下面
这个例子显示了这两种不同的临时表
create global temporary table temp_table_session
2 on commit preserve rows
3 as
4 select * from scott.emp where 1=0
这是创建会话临时表
ON COMMIT DELETE ROWS子句使得这是一个基于事务的临时表
如果你曾在SQL Server和/或Sybase中用过临时表,现在所要考虑的主要问题是:不应该执行SELECT X, Y, Z INTO #TEMP FROM SOME_TABLE来动态创建和填充一个临时表,而应该:将所有全局临时表只创建一次,作为应用安装的一部分,就像是创建永久表一样。在你的过程中,只需执行INSERT INTO TEMP(X, Y, Z) SELECT X, Y, Z FROM SOME_TABLE。
归根结底,这里的目标是:不要在运行时在你的存储过程中创建表。这不是Oracle中使用临时表的正确做法。DDL是一种代价昂贵的操作:你要全力避免在运行时执行这种操作。一个应用的临时表应该在应用安装期间创建,绝对不要在运行时创建。
临时表可以有永久表的许多属性。它们可以有触发器、检查约束、索引等。但永久表的某些特性在临时表中并不支持,这包括:
q 不能有引用完整性约束。临时表不能作为外键的目标,也不能在临时表中定义外键。
q 不能有NESTED TABLE类型的列。 在Oracle 9i及以前版本中, VARRAY类型的列也不允许;
不过Oracle 10g中去掉了这个限制。
q 不能是IOT。
q 不能在任何类型的聚簇中。
q 不能分区。
q 不能通过ANALYZE表命令生成统计信息。
在 所有数据库中,临时表的缺点之一是优化器不能正常地得到临时表的真实统计。使用基于代价的优化器(cost-based optimizer,CBO)时,有效的统计对于优化器的成败至关重要。如果没有统计信息,优化器就只能对数据的分布、数据量以及索引的选择性作出猜测。 如果这些猜测是错的,为查询生成的查询计划(大量使用临时表)可能就不是最优的。在许多情况下,正确的解决方案是根本不使用临时表,而是使用一个 INLINE VIEW(要看INLINE VIEW的例子,可以查看前面运行的SELECT,它就有两个内联视图)。采用这种方式,Oracle可以访问一个表的所有相关统计信息,而且得出一个最 优计划
我经常发现,人们之所以使用临时表,是因为他们在其他数据库中了解到一个查询中联结太多的表是一件“不好的事情”。但在Oracle开发中,必须把这个知识忘掉。不要想着你比优化器要聪明,来一个查询分解成3个或4个查询,将其子结果存储在临时表中,然后再合并这些临时表;正确的做法是应该编写一个查 询,直接回答最初的问题。在一个查询中引用多个表是可以的;Oracle中在这个方面不需要临时表的帮助
临时表小结
如 果应用中需要临时存储一个行集由其他表处理(可能对应一个会话,也可能对应一个事务),临时表就很有用。不要把临时表作为一个分解大查询的方法,即拿到一 个大查询,把它“分解”为几个较小的结果集,然后再把这些结果集合并在一起(这看来是其他数据库中最常见的临时表用法)。实际上,你会发现,在几乎所有的 情况下。Oracle中如果将一个查询分解为较小的临时表查询,与原来的一个查询相比,只会执行得更慢。我就经常看到人们这样做,如果有可能把对临时表的 一系列INSERT重写为一个大查询(SELECT),所得到的单个查询会比原来的多步过程快得多。
临 时表会生成少量的redo,但是确实还是会生成redo,而且没有办法避免。这些redo是为回滚数据生成的,而且在最典型的情况下,可以忽略不计。如果 只是对临时表执行INSERT和SELETE,生成的redo量几乎注意不到。只有对临时表执行大量DELETE和UPDATE时,才会看到生成大量的 redo。 如果精心设计,可以在临时表上生成CBO使用的统计信息;不过,可以使用DBMS_STATS包对临时表上的统计给出更好的猜测,或者由优化器使用动态采样在硬解析时动态收集。