本章提要
-----------------------------------
各种数据库表的讨论, 并介绍什么情况使用哪种表
情调表的物理存储特征
-----------------------------------
表类型
堆组织表, 普通标准表, 数据以堆的方式管理, 增加数据时, 会使用段中找到的第一个能放下此数据的自由空间.
索引组织表, 表按索引结构存储, 这就强制要求行本身有某种物理顺序, 在堆中, 只要放的下, 数据可以放在任何
位置, 而索引组织表, 数据要根据主键有序的存储
索引聚簇表, 聚簇是指一个活多个表组成的组, 这些表物理地存储在相同的数据块上, 有相同聚簇键值的所有行会
相邻地物理存储. 这种结构可以实现两个目标, 首先,多个表可以物理存储在一起, 一般而言, 你可能认为
一个表的数据就在一个数据库块上, 但是对于聚簇表, 可能把多个表的数据存储在同一个块上. 其次, 包含
相同聚簇键值(如 deptno=10)的所有数据会物理地存储在一起, 聚簇键使用B*树索引建立.
散列聚簇表, 类似于聚簇表, 但是不使用B*树索引按聚簇键来定位数据, 散列聚簇将键散列到聚簇上, 如果需要频繁的
通过键的相等性比较来读取数据, 散列聚簇表就很适用.
有序散列聚簇表, 10g后新增的, 你的行按某个键值(如 customer_id)散列, 而与该键相关的一系列记录以某种有序顺序
到达(因此这些记录基于时间戳的记录), 例如, 客户在你的订单输入系统中下订单, 这些订单会按先先进先出的
方式获取和处理, 在这样的系统中, 有序散列聚簇就是适用的数据结构.
嵌套表,
临时表, 这些表存储的是事务期间或会话期间的"草稿"数据. 每个会话只能看到这个会话分配的区段, 它从不会看到其他
任何会话中创建的任何数据.
对象表, 基于某种对象类型创建, 它们拥有非对象表所没有的特殊属性.
外部表, 放在平常的操作系统文件中, 但是查询时, 就好像这个文件也是数据库中平常表一样. 外部表向数据库加载数据最
有用.
一般来讲, 以上所有的表限制1000列(不过最好列数远小于这个数), 一个表空间最多有 1022个文件, 假设你有一个典型的表
空间, 使用32G的文件大小, 也就是说, 全部大小为 1022乘以 32G.
相关术语
段, 占用磁盘上存储空间的一个对象, 有多种类型:
聚簇, 又分为 B*聚簇 和 散列 聚簇
表, 通常与索引段联合使用
表分区, table partition
索引,
索引分区, index partition(类似表分区)
lob分区, 大对象的分区存储, 与以上的table 和 index 分区类似
嵌套表
回滚段, undo
举例来说, 一个索引有可能是一个段, 我们也可以把一个索引划分到不同的段中, 所以, 索引对象本身只是一个定义, 而
不是一个物理段, 索引可能由多个索引分区组成, 而每个索引分区是一个段. 不过, 最常见的情况是, 表是一个段, 索引
也是一个段.
段空间管理, 这里推荐自动管理 ASSM(Auotomatic Segment Space Management), 你只需控制与空间使用的相关的一个
参数, PCTFREE.(另外还有, 如果需要可以设置 BUFFER_POOL, INITRANS, MAXTRANS)
高水位, HWM, oracle在全表扫描时会扫描HWM以下的所有块, 对于 ASSM表空间中, 还有一个低HWM
对于ASSM, HWM推进时, oracle 并不会立刻格式化所有块, 只是在第一次使用这些块时才完成格式化, 第一次使用会发生在
数据库向给定块插入记录时, 对于ASSM, 数据会插入到块中的任意水位线(在低水位线和高水位线之间), 因此在这个区域的
许多块不会被格式化, 所以, 全扫描一个段时, 必须知道要读取的块是否"安全"或是否未格式化(这说明, 其中不包含有
意义的信息, 不能对其进行处理), 为了避免表中每一个块都要必须经过这种安全/不安全检查, oracle同时维护了一个低HWM
和一个HWM, oracle 会全表扫描至HWM, 对于低HWM以下的所有块会直接读取并加以处理.
freelist, 在MSSM表空间中(手动管理表空间, 这里不推荐使用, 只是简单了解).
PTCFREE, PCTUSED, PCTFREE: 块上应该保留多大的空间来完成将来的更新. PCTUSED在ASSM中被忽略.
根据表的用途和结构的不同,可以设置不同的pctfree, 设置太高浪费空间, 设置太小, 会导致行迁移.
行迁移, 是指由于某一行变的太大, 无法再与其余的行一同放在创建这一行的块中,
当然, 如果通过自由空间的合并, 可以得到满足的空间来放数据, 那么就不会发生行迁移.
oracle 不能简单的移动这一行, 它必须留下一个转发地址, 对于分区表有一个特列,更新分区表时, 会改变rowid.
为什么这样会带来问题? 你的应用绝对不会知道存在行迁移, 你使用的SQL也没有任何不同, 行迁移只会影响性能. 如果你
通过一个索引来读这一行, 索引会指向原来的块, 那个块再指向这个新块, 乍一看, 好像只多了一次I/O读取,问题不大,
但是, 如果表中有很多这样的行, 而且大量用于在访问这些行, 访问速度就开始变慢, 如果这一行所在的块, 又"满了",发生
再次迁移, 如果先前的块有空间的话,那么会将这个块迁移回原来的块上. 这样的效果就是"未迁移", 但是,如果原来块上的
空间不够, 那么就会迁移到别的块, 并修改原来块的指针, 变为这个新的地址. 因此, 行迁移总是只会涉及一层间接性.
logging 和 nologging
通常对象都采用 logging 方式创建, 这说明对象上完成的操作只要能生成 redo 都会生成 redo. nologging 则允许该对象
完成某些操作时可以不生成 redo.
initrans 和 maxtrans
段中每个块都有一个块首部, 这个块首部有一个事务表, 事务表中建立一些条目来描述哪些事务将块上的哪些行/元素锁定.
堆组织表
应用中 99% 以上的情况下使用堆组织表, 执行 create table 时, 默认得到的表类型就是堆组织表, 堆中的数据会以一种显然
随机的方式管理, 数据会放在最合适的地方, 而不是以某种特定顺序来防止. 许多人希望能按数据放入表中的顺序从表中取出数据,
但是对于堆, 这是无法保证的.
全表扫描时, 会按命中的顺序来获取数据, 而不是插入的顺序, 一般来讲, 数据库表本质上是无序的数据集合. 同一个查询可能会
以不同的顺序取出数据, 不要过分依赖查询得到的行顺序, 除非查询中有一个 order by 语句.
创建表是很复杂的, 有很多选项, 利用如下技巧:
首先, 尽可能简单的创建表,
然后, 使用标准内置包 DBMS_METADATA, 查看这个表的定义, 并查看详细语法,
-- 首先尽可能简单的创建表 create table t ( x int primary key, y date, z clob ) / -- 然后查看这个表的详细定义 select dbms_metadata.get_ddl( 'TABLE', 'T' ) from dual;
这个技巧的好处是, 它显示了 create table 语句的许多选项, 我只是提供了数据类型, oracle 会为我生成详细的"版本", 现在
我可以定制这个详细的版本(进行部分修改), 对于 ASSM 创建表, 有3个重要选项.
1) PCTFREE
2) INITRANS
3) COMPRESS/NOCOMPRESS, 在直接路径操作或传统路径操作(比如insert)期间启用或禁用表数据的压缩.
其他表并不常用,这里比介绍了, 用时再说.
临时表
用于保存事务或会话期间的中间结果集. 临时表中保存的数据只对当前会话可见, 所有会话都看不到其他会话的数据; 即使当前会话
已经提交(commit)了数据, 别的会话也看不到它的数据.
创建临时表:
create global temporary table temp_table_session on commit preserve rows as select * from scott.emp where 1=0 / -- on commit preserve rows 子句使得这是一个基于会话的临时表, 在我的会话 -- 断开连接之前, 这些行会一直存在于这个临时表中, 只有我的会话能看到这些行 -- 即使我 commit, 其他会话也无法看到"我的"行 create global temporary table temp_table_transaction on commit delete rows as select * from scott.emp where 1=0 / -- on commit delete rows 子句使得这是一个基于事务的临时表, 我的会话提交时, -- 临时表中的行就不见了.
不要在运行时在你的存储过程中创建表, 这不是 oracle 使用临时表的正确做法, DDL 是一种代价昂贵的操作. 一个应用的临时表,
应该在开始安装时创建, 绝对不要在运行时创建.