ORACLE 索引聚簇表的数据加载(原) 一、首先介绍一下索引聚簇表的工作原理 聚簇是指:如果一组表有一些共同的列,则将这样一组表存储在 相同的数据库块中;聚簇还表示把相关的数据存储在同一个块上。利 用聚簇,一个块可能包含多个表的数据。概念上就是如果两个或多个 表经常做链接操作,那么可以把需要的数据预先存储在一起。聚簇还 可以用于单个表,可以按某个列将数据分组存储。 更加简单的说,比如说,EMP 表和 DEPT 表,这两个表存储在不 同的 segment 中,甚至有可能存储在不同的 TABLESPACE 中,因此, 他们的数据一定不会在同一个 BLOCK 里。 而我们有会经常对这两个表 做关联查询,比如说:select * from emp,dept where emp.deptno = dept.deptno .仔细想想,查询主要是对 BLOCK 的操作,查询的 BLOCK 越多,系统 IO 就消耗越大。如果我把这两个表的数据聚集在少量的 BLOCK 里,查询效率一定会提高不少。 比如我现在将值 deptno=10 的所有员工抽取出来, 并且把对应的 部门信息也存储在这个 BLOCK 里(如果存不下了,可以为原来的块串 联另外的块) 。这就是索引聚簇表的工作原理。 二、创建过程 索引聚簇表是基于一个索引聚簇(index cluster)创建的。里面记 录的是各个聚簇键。聚簇键和我们用得做多的索引键不一样,索引键 指向的是一行数据,聚簇键指向的是一个 ORACLE BLOCK。我们可以先 通过以下命令创建一个索引簇。 SQL> conn scott/tiger 已连接。 SQL> desc dept 名称 是否为空? 类型 ----------------------------------------- -------- ---------------------------DEPTNO DNAME LOC NOT NULL NUMBER(2) VARCHAR2(14) VARCHAR2(13) SQL> create cluster emp_dept_cluster 2 ( deptno number(2) ) 3 size 1024 4 / 簇已创建。 这个名字可以用户定义,不一定叫 deptno,数据类型必须和需要 使用这个聚簇的数据类型一致 NUMBER(2)。在这里最关键的一个参数 是 size。这个选项原来告诉 Oracle:我们希望与每个聚簇键值关联大 约 1024 字节的数据(1024 对于一般的表一条数据没问题) ,Oracle 会 在用这个数据库块上设置来计算每个块最 多能放下多少个聚簇键。 假 设块大小为 8KB,Oracle 会在每个数据库块上放上最多 7 个聚簇键, 也就是说,对应部门 10、20、30、40、50、60 和 70 的数据会放在一 个块上,一旦插入部门 80,就会使用一个新块。存放的数据是和插入 顺序相关的。 因此, SIZE 测试控制着每块上聚簇键的最大个数。 这是对聚簇空 间利用率影响最大的因素。如果把这个 SIZE 设置得太高,那么每个 块上的键就会很少(单位 BLOCK 可以存的聚簇键就少了) ,我们会不 必要地使用更多的空间。 如果设置得太低, 又会导致数据过分串链 (一 个聚簇键不够存放一条数据) ,这又与聚簇本来的目的不符,因为聚 簇原本是为了把所有相关数据都存储在一个块上。 向聚簇中放数据之前,需要先对聚簇建立索引。可以现在就在 聚簇中创建表,但是由于我们想同时创建和填充表,而有数据之前必 须有一个聚簇索引,所以我们先来建立聚簇索引。 聚簇索引的任务是拿到一个聚簇键值,然后返回包含这个键的 块的块地址。实际上这是一个主键,其中每个聚簇键值指向 聚簇本 身中的一个块。因此,我们请求部门 10 的数据时,Oracle 会读取聚 簇键,确定相应的块地址,然后读取数据。聚簇键索引如下创建: SQL> create index emp_dept_cluster_idx 2 on cluster emp_dept_cluster 3 / 索引已创建。 现在可以创建表了: SQL> conn segment_study/liugao 已连接。 SQL> create table dept 2 ( deptno number(2) primary key, 3 4 5 ) 6 cluster emp_dept_cluster(deptno) 7 / 表已创建。 SQL> create table emp 2 ( empno 3 4 5 6 7 8 9 10 ) 11 cluster emp_dept_cluster(deptno) 12 / 表已创建。 ename job mgr number primary key, varchar2(10), varchar2(9), number, dname varchar2(14), loc varchar2(13) hiredate date, sal comm number, number, deptno number(2) constraint emp_fk references dept(deptno) 我们可以通过一下 SQL 语句查看创建: SQL> select cluster_name, table_name 2 from user_tables 3 where cluster_name is not null 4 order by 1; CLUSTER_NAME TABLE_NAME ------------------------------ ----------------------------EMP_DEPT_CLUSTER EMP_DEPT_CLUSTER DEPT EMP 现在,聚簇,聚簇索引,聚簇索引表都已经建立完成。 三、加载数据 向聚簇索引表中加载数据是个很讲究的事情,处理方法不对,会 使得聚簇的功能发挥不完全,降低查询性能。 方法 1: 首先, 我增加一个很大的列 char (1000) ,加这个列是为了让 EMP 行远远大于现在的大小。使得一个 1024 的聚簇无法存储一行记录。 不能加 varchar2(1000),因为 ORACLE 对 varchar2 存储的原则是能省 就省,如果数据不到 1000,不会分配 1000 的空间的。char 则是有多 少用多少。呵呵。 SQL> begin 2 for x in ( select * from scott.dept ) 3 4 5 6 7 8 9 10 loop insert into dept values ( x.deptno, x.dname, x.loc ); insert into emp select * from scott.emp where deptno = x.deptno; end loop; 11 end; 12 / begin * 第 1 行出现错误: ORA-02032: 聚簇表无法在簇索引建立之前使用 ORA-06512: 在 line 4 SQL> create index emp_dept_cluster_idx 2 on cluster emp_dept_cluster 3 ; 索引已创建。 SQL> alter table emp disable constraint emp_fk; 表已更改。 SQL> truncate cluster emp_dept_cluster; 簇已截断。 SQL> alter table emp enable constraint emp_fk; 表已更改。 SQL> alter table emp add data char(1000); 表已更改。 上面的执行错误说明聚簇表无法在簇索引建立之前使用。 首先我们通过先加载 emp 表,后加载 dept 表的方式。 SQL> insert into dept 2 select * from scott.dept; 已创建 4 行。 SQL> insert into emp 2 select emp.*, '*' from scott.emp; 已创建 14 行。 然后做一个查询, 通过 dbms_rowid.rowid_block_number 可以查看此 数据所在的 BLOCK ID,如果 dept 和 emp 存储的行数据不是一个 BLOCK ID ,则标记一个'*'.查询结果如下: SQL> select dept_blk, emp_blk, 2 3 4 5 6 from ( select dbms_rowid.rowid_block_number(dept.rowid) dept_blk, case when dept_blk <> emp_blk then '*' end flag, deptno dbms_rowid.rowid_block_number(emp.rowid) emp_blk, 7 8 9 10 11 dept.deptno from emp, dept where emp.deptno = dept.deptno ) order by deptno 12 / DEPT_BLK EMP_BLK F DEPTNO ---------- ---------- - ---------85 85 85 85 85 85 85 85 85 85 85 DEPT_BLK 86 * 86 * 87 * 85 87 * 86 * 85 86 * 85 86 * 85 EMP_BLK F 10 10 10 20 20 20 20 20 30 30 30 DEPTNO ---------- ---------- - ---------85 86 * 30 85 85 已选择 14 行。 85 85 30 30 我们发现,通过先插入 emp 数据,再插入 dept 数据,导致大部分的 emp 和 dept 的数据都不在一个 block 上,这不是我们使用聚簇索引 的目的。 方法二: 先处理一下刚才插入的数据: SQL> truncate cluster emp_dept_cluster; truncate cluster emp_dept_cluster * 第 1 行出现错误: ORA-02266: 表中的唯一/主键被启用的外键引用 SQL> alter table emp disable constraint emp_fk; 表已更改。 SQL> truncate cluster emp_dept_cluster; 簇已截断。 SQL> alter table emp enable constraint emp_fk; 表已更改。 然后使用以下的方式插入数据: SQL> begin 2 3 4 5 6 7 8 9 10 for x in ( select * from scott.dept ) loop insert into dept values ( x.deptno, x.dname, x.loc ); insert into emp select emp.*, 'x' from scott.emp where deptno = x.deptno; end loop; 11 end; 12 / PL/SQL 过程已成功完成。 执行上面统一的 SQL。 SQL> select dept_blk, emp_blk, 2 3 4 from ( case when dept_blk <> emp_blk then '*' end flag, deptno 5 select dbms_rowid.rowid_block_number(dept.rowid) dept_blk, 6 7 8 9 dbms_rowid.rowid_block_number(emp.rowid) emp_blk, dept.deptno from emp, dept where emp.deptno = dept.deptno 10 11 ) order by deptno 12 / DEPT_BLK EMP_BLK F DEPTNO ---------- ---------- - ---------85 85 85 85 85 85 85 85 86 86 86 DEPT_BLK 85 85 85 85 85 85 86 * 86 * 86 86 86 EMP_BLK F 10 10 10 20 20 20 20 20 30 30 30 DEPTNO ---------- ---------- - ---------86 86 86 已选择 14 行。 86 87 * 87 * 30 30 30 咱们发现,大部分的数据都在同一个块中。原来这才是想聚簇表里添 加数据的最佳方法。 为什么会有这样的差别呢?? 当我们通过第一种方法时,有一个问题,由于 dept 表的行在聚簇中 占用空间很小,但是剩余的空间确不能存一条 dept 的数据(应为我 们添加了 char(1000)了) 。这样就会在那些聚簇 键块上导致过度 的串链。Oracle 会把包含这些信息的一组块串链或链接起来。如果 同时加载对应一个给定聚簇键的所有数据,就能尽可能紧地塞满块, 等空间 用完时再开始一个新块。 四、什么时候不应该使用聚簇? 1)如果预料到聚簇中的表会大量修改:必须知道,索引聚簇会对 DML 的性能产生某种负面影响(特别是 INSERT 语句) 。管理聚簇中的数据 需要做更多的工作。 2)如果需要对聚簇中的表执行全表扫描:不只是必须对你的表中的 数据执行全面扫描,还必须对(可能的)多个表中的数据进行全面扫 描。由于需要扫描更多的数据,所以全表扫描耗时更久。 3) 如果你认为需要频繁地 TRUNCATE 和加载表: 聚簇中的表不能截除。 这是显然的,因为聚簇在一个块上存储了多个表,必须删除聚簇表中 的行。 因此,如果数据主要用于读(这并不表示“从来不写”;聚簇表 完全可以修改) ,而且要通过索引来读(可以是聚簇键索引,也可以 是聚簇表上的其他索引) ,另外频繁地把这些信息联结在一起,此时 聚簇就很适合。