Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4478136
  • 博文数量: 109
  • 博客积分: 10011
  • 博客等级: 上将
  • 技术积分: 2457
  • 用 户 组: 普通用户
  • 注册时间: 2006-10-18 19:04
文章分类

全部博文(109)

文章存档

2011年(1)

2010年(10)

2009年(36)

2008年(62)

我的朋友

分类: Oracle

2009-08-31 10:00:40

本文部分思想和案例是源自 Thomas Kyte 的《深入数据库体系结构》一书,也加入了部分作者个人观点和作者的实践。
 
一:首先介绍一下索引聚簇表的工作原理:
   聚簇是指:如果一组表有一些共同的列,则将这样一组表存储在相同的数据库块中;聚簇还表示把相关的数据存储在同一个块上。利用聚簇,一个块可能包含多个表的数据。概念上就是如果两个或多个表经常做链接操作,那么可以把需要的数据预先存储在一起。聚簇还可以用于单个表,可以按某个列将数据分组存储。
   更加简单的说,比如说,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                                    NOT NULL NUMBER(2)
 DNAME                                              VARCHAR2(14)
 LOC                                                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    dname  varchar2(14),
  4    loc    varchar2(13)
  5  )
  6  cluster emp_dept_cluster(deptno)
  7  /
表已创建。

SQL> create table emp
  2  ( empno    number primary key,
  3    ename    varchar2(10),
  4    job      varchar2(9),
  5    mgr      number,
  6    hiredate date,
  7    sal      number,
  8    comm     number,
  9    deptno number(2) constraint emp_fk references dept(deptno)
 10  )
 11  cluster emp_dept_cluster(deptno)
 12  /
表已创建。
 
我们可以通过一下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               DEPT
EMP_DEPT_CLUSTER               EMP
现在,聚簇,聚簇索引,聚簇索引表都已经建立完成。
 
三:加载数据。
      向聚簇索引表中加载数据是个很讲究的事情,处理方法不对,会使得聚簇的功能发挥不完全,降低查询性能。
方法1:
     首先,我增加一个很大的列char(1000),加这个列是为了让EMP行远远大于现在的大小。使得一个1024的聚簇无法存储一行记录。不能加varchar2(1000),因为ORACLE对varchar2存储的原则是能省就省,如果数据数据不到1000,不会分配1000的空间的。char则是有多少用多少。呵呵。
SQL> begin
  2      for x in ( select * from scott.dept )
  3      loop
  4          insert into dept
  5          values ( x.deptno, x.dname, x.loc );
  6          insert into emp
  7          select *
  8            from scott.emp
  9           where deptno = x.deptno;
 10      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         case when dept_blk <> emp_blk then '*' end flag,
  3             deptno
  4    from (
  5  select dbms_rowid.rowid_block_number(dept.rowid) dept_blk,
  6         dbms_rowid.rowid_block_number(emp.rowid) emp_blk,
  7         dept.deptno
  8    from emp, dept
  9   where emp.deptno = dept.deptno
 10         )
 11   order by deptno
 12  /
  DEPT_BLK    EMP_BLK F     DEPTNO
---------- ---------- - ----------
        85         86 *         10
        85         86 *         10
        85         87 *         10
        85         85           20
        85         87 *         20
        85         86 *         20
        85         85           20
        85         86 *         20
        85         85           30
        85         86 *         30
        85         85           30
  DEPT_BLK    EMP_BLK F     DEPTNO
---------- ---------- - ----------
        85         86 *         30
        85         85           30
        85         85           30
已选择14行。
我们发现,通过先插入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      for x in ( select * from scott.dept )
  3      loop
  4          insert into dept
  5          values ( x.deptno, x.dname, x.loc );
  6          insert into emp
  7          select emp.*, 'x'
  8            from scott.emp
  9           where deptno = x.deptno;
 10      end loop;
 11  end;
 12  /
PL/SQL 过程已成功完成。
执行上面统一的SQL。
SQL> select dept_blk, emp_blk,
  2         case when dept_blk <> emp_blk then '*' end flag,
  3             deptno
  4    from (
  5  select dbms_rowid.rowid_block_number(dept.rowid) dept_blk,
  6         dbms_rowid.rowid_block_number(emp.rowid) emp_blk,
  7         dept.deptno
  8    from emp, dept
  9   where emp.deptno = dept.deptno
 10         )
 11   order by deptno
 12  /
  DEPT_BLK    EMP_BLK F     DEPTNO
---------- ---------- - ----------
        85         85           10
        85         85           10
        85         85           10
        85         85           20
        85         85           20
        85         85           20
        85         86 *         20
        85         86 *         20
        86         86           30
        86         86           30
        86         86           30
  DEPT_BLK    EMP_BLK F     DEPTNO
---------- ---------- - ----------
        86         86           30
        86         87 *         30
        86         87 *         30
已选择14行。
咱们发现,大部分的数据都在同一个块中。原来这才是想聚簇表里添加数据的最佳方法。
为什么会有这样的差别呢??
当我们通过第一种方法时,有一个问题,由于dept表的行在聚簇中占用空间很小,但是剩余的空间确不能存一条dept的数据(应为我们添加了char(1000)了)。这样就会在那些聚簇 键块上导致过度的串链。Oracle会把包含这些信息的一组块串链或链接起来。如果同时加载对应一个给定聚簇键的所有数据,就能尽可能紧地塞满块,等空间 用完时再开始一个新块。

四:什么时候不应该使用聚簇?
1) 如果预料到聚簇中的表会大量修改:必须知道,索引聚簇会对DML的性能产生某种负面影响(特别是INSERT语句)。管理聚簇中的数据需要做更多的工作。
2) 如果需要对聚簇中的表执行全表扫描:不只是必须对你的表中的数据执行全面扫描,还必须对(可能的)多个表中的数据进行全面扫描。由于需要扫描更多的数据,所以全表扫描耗时更久。
3) 如果你认为需要频繁地TRUNCATE和加载表:聚簇中的表不能截除。这是显然的,因为聚簇在一个块上存储了多个表,必须删除聚簇表中的行。
    因此,如果数据主要用于读(这并不表示“从来不写”;聚簇表完全可以修改),而且要通过索引来读(可以是聚簇键索引,也可以是聚簇表上的其他索引),另外会频繁地把这些信息联结在一起,此时聚簇就很适合。
 
    本人认为适合OLAP的应用中,因为,olap数据几乎没有修改,这是其一,其二是很少对表做全部扫描,机会没有,没有人会select * from 事实表。第三,olap应用主要的查询是维表和事实表的关联查询。聚簇索引再适合不过了。大家认为了??
Chris_liu is waiting for your response ,Don't hestite to reply me .....
 
 
 
阅读(1884) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~