分区表索引介绍

局部索引:

局部前缀索引(local prefixed index):在这些索引中,分区键在索引定义的前几列上。例如,一个表在名为LOAD_DATE 的列上进行区间分区,
                                     该表上的局部前缀索引就是采用LOAD_DATE作为其索引列列表中的第一列。
局部非前缀索引(local nonprefixed index):这些索引不以分区键作为其列列表的前几列。索引可能包含分区键列,也可能不包含。
    这两类索引都可以进行分区消除,前提是查询的条件中包含索引分区键,它们都支持惟一性(只要局部非前缀索引包含分区键列)。
    局部索引与表的分区数一致,如果新增一个分区,新增加的分区局部索引也会自动创建。全局索引则不行(即需要重建全局索引)。
    
一、分区消除介绍:
--下面通过实验来说明索引的分区消除
--创建一个分区表
CREATE TABLE partitioned_table ( a int, b int, data char(20))
 PARTITION BY RANGE (a)
 ( PARTITION part_1 VALUES LESS THAN(2) tablespace gcomm,      --以a字段进行分区,小于等于2的存在分区1,小于等于3的存在分区2
   PARTITION part_2 VALUES LESS THAN(3) tablespace gmapdata )
--创建一个本地前缀索引   
create index local_prefixed on partitioned_table (a,b) local;
--创建一个本地非前缀索引
create index local_nonprefixed on partitioned_table (b) local;
--向表中插入数据
insert into partitioned_table select mod(rownum-1,2)+1, rownum, 'x' from all_objects;
--分析表
begin dbms_stats.gather_table_stats ( user,'PARTITIONED_TABLE',cascade=>TRUE );end;

--以sys用户登录后 将gmapdata表空间置为离线
alter tablespace gmapdata offline;--分区2的数据包括其索引等都被置为离线状态

select * from partitioned_table where a = 1 and b = 1;
A        B     DATA
----  ------ --------------------
1       1        x
--将之前的plan_table表的数据清除
delete from plan_table;
--生成统计信息
explain plan for select * from partitioned_table where a = 1 and b = 1;
--查看统计信息结果
select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1622054381
--------------------------------------------------------------------------------
| Id  | Operation                                                  | Name                       | Rows  | Bytes | Pstart| Pstop
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                  |                                 |     1   |    28 |    
|   1 |  PARTITION RANGE SINGLE                        |                                 |     1   |    28 |    1      1
|   2 |   TABLE ACCESS BY LOCAL INDEX ROWID  | PARTITIONED_TABLE  |     1   |    28 |    1      1
|*  3 |    INDEX RANGE SCAN                              | LOCAL_PREFIXED       |     1   |         |    1      1
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("A"=1 AND "B"=1)
注:可以进行查询,可以通过本地前缀索引将分区2消除 由于分区2采用的表空间为gmapdata,而这个表空间在上述已将其离线,通过本地前缀索引在查询的时候将分区2消除,只在第一个分区进行查询,因此该查询能够成功查询。

再看下面的一个查询:  
select * from partitioned_table where b = 1;
提示:ora-00376:此时无法读取文件11
      ora-01110:数据文件11:‘D:\ORACE|PRODUCT\10.2.0\ORADATA\FGISDBGMAPDATA.DBF’
delete from plan_table;
explain plan for select * from partitioned_table where b = 1;
select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 440752652
--------------------------------------------------------------------------------
| Id  | Operation                                                      | Name                        | Rows  | Bytes |  Pstart| Pstop
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                                   |                                     |     1 |    28 |   
|   1 |  PARTITION RANGE ALL                              |                                      |     1 |    28 |    1       2
|   2 |   TABLE ACCESS BY LOCAL INDEX ROWID   | PARTITIONED_TABLE      |     1 |    28 |    1       2
|*  3 |    INDEX RANGE SCAN                               | LOCAL_NONPREFIXED     |     1 |        |    1       2
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("B"=1)
注:当查询谓词只有b,即采用非前缀索引,而且查询的条件中又不含分区键a,因此在查询时无法将分区2消除,导致在查询分区2时提示数据文件不在。

将本地前缀索引删掉后:   
drop index local_prefixed;
select * from partitioned_table where a = 1 and b = 1;
A        B     DATA
----  ------ --------------------
1       1        x
delete from plan_table;
explain plan for select * from partitioned_table where a = 1 and b = 1;
select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 904532382
--------------------------------------------------------------------------------
| Id  | Operation                          | Name              | Rows  | Bytes | Pstart| Pstop
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                   |     1 |    28 |
|   1 |  PARTITION RANGE SINGLE            |                   |     1 |    28 |   1       1
|*  2 |   TABLE ACCESS BY LOCAL INDEX ROWID| PARTITIONED_TABLE |     1 |    28 |   1       1
|*  3 |    INDEX RANGE SCAN                | LOCAL_NONPREFIXED |     1 |       |   1       1
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("A"=1)
   3 - access("B"=1)
  
注:本地前缀索引删除后,采用本地非前缀索引进行如上查询也可以成功。可见本地非前缀索引也可以进行消除分区,主要取决于谓词。该表利用a字段
进行分区,因此主要谓词中有a字段的查询,就可以成功查询。



二、局部索引和惟一约束介绍:

CREATE TABLE partitioned
 ( load_date date, id int, constraint partitioned_pk primary key(id) )
 PARTITION BY RANGE (load_date)
 ( PARTITION part_1 VALUES LESS THAN( to_date('01/01/2000','dd/mm/yyyy') ) ,
   PARTITION part_2 VALUES LESS THAN( to_date('01/01/2001','dd/mm/yyyy') ))

select segment_name, partition_name, segment_type from user_segments where segment_name like 'PARTITIONED%';

SEGMENT_NAME              PARTITION_NAME       SEGMENT_TYPE
--------------------  ---------------------  ------------------
PARTITIONED               PART_1                TABLE PARTITION
PARTITIONED               PART_2                TABLE PARTITION
PARTITIONED_PK                                  INDEX
注:PARTITIONED_PK 索引没有进行分区,因此可以保证唯一性

删掉表重新创建并建立一个本地索引后在创建一个唯一索引

drop table partitioned
CREATE TABLE partitioned ( timestamp date, id int)
 PARTITION BY RANGE (timestamp)
 (PARTITION part_1 VALUES LESS THAN( to_date('01-1-2000','dd-mm-yyyy') ) ,
  PARTITION part_2 VALUES LESS THAN( to_date('01-1-2001','dd-mm-yyyy') )
 )

create index partitioned_idx on partitioned(id) local;--创建一个本地索引

select segment_name, partition_name, segment_type from user_segments where segment_name like 'PARTITIONED%';

SEGMENT_NAME                PARTITION_NAME                 SEGMENT_TYPE
------------------------ ------------------------------ ------------------
PARTITIONED                 PART_1                         TABLE PARTITION
PARTITIONED                 PART_2                         TABLE PARTITION
PARTITIONED_IDX             PART_1                         INDEX PARTITION
PARTITIONED_IDX             PART_2                         INDEX PARTITION

alter table partitioned add constraint partitioned_pk primary key(id);--在id上增加一个全局索引
提示:此列列表已有索引
注:分区索引无法 保证唯一性,因为如果要保证分区索引的唯一性,即分区1有id=1,那么分区2中就不能有id=1,而我们如果做了这个限制,往不同
分区进行插数据就会降低分区表的灵活性。



全局索引

    全局索引使用一种有别于底层表的机制进行分区。表可以按一个TIMESTAMP 列划分为10 个分区,而这个表上的一个全局索引可以按REGION 列划分
为5 个分区。与局部索引不同,全局索引只有一类,这就是前缀全局索引(prefixed global index)。如果全局索引的索引键未从该索引的分区键开始
,这是不允许的。这说明,不论用什么属性对索引分区,这些属性都必须是索引键的前几列。

drop table partitioned
CREATE TABLE partitioned ( timestamp date, id int )
 PARTITION BY RANGE (timestamp)
 ( PARTITION part_1 VALUES LESS THAN ( to_date('01-1-2000','dd-mm-yyyy') ) ,
   PARTITION part_2 VALUES LESS THAN ( to_date('01-1-2001','dd-mm-yyyy') )
 )

create index partitioned_index on partitioned(id) GLOBAL
 partition by range(id)
 (partition part_1 values less than(1000),
  partition part_2 values less than (MAXVALUE) --全局索引必须指定最大值,否则会提示:ORA-14021:必须指定所有列的MAXVALUE
 )
注:全局索引有一个要求,即最高分区(最后一个分区)必须有一个值为MAXVALUE 的分区上界。这可以确保底层表中的所有行都能放在这个索引中。

全局索引可以创建一个唯一索引:

alter table partitioned add constraint partitioned_pk primary key(id);--创建唯一索引成功
注:该唯一索引是通过创建的全局索引来保证唯一,可以通过删除其索引的错误来说明
drop index partitioned_index;提示:ora-02429:无法删除用于强制唯一/主键的索引。

--以下例子说明了全局索引必须是前缀的
create index partitioned_index2 on partitioned(timestamp,id) GLOBAL
 partition by range(id)--以id为分区键 那么其索引就必须将id置到最前面
 (partition part_1 values less than(1000),
  partition part_2 values less than (MAXVALUE)
 )
提示:ORA-14038:全局分区索引必须是前缀

数据仓库与全局索引:
数据仓库一般是通过数据的滑入划出进行管理(即旧数据划出,新数据滑入)。一个分区表中,如果进行分区的增删改操作会造成全局索引失效。因此,
采用何种索引要根据系统的要求。

实验1:分区的滑入滑出导致全局索引失效,局部索引仍有效
--创建分区表  
CREATE TABLE partitioned ( timestamp date, id int )
 PARTITION BY RANGE (timestamp)
 ( PARTITION fy_2004 VALUES LESS THAN ( to_date('01-1-2005','dd-mm-yyyy') ) ,
  PARTITION fy_2005 VALUES LESS THAN ( to_date('01-1-2006','dd-mm-yyyy') )
 )
--对两个分区都插入数据
insert into partitioned partition(fy_2004)
  select to_date('31-12-2004', 'dd-mm-yyyy') - mod(rownum,360),
         object_id
    from all_objects;
         
insert into partitioned partition(fy_2005)
  select to_date('31-12-2005', 'dd-mm-yyyy') - mod(rownum,360),
         object_id
    from all_objects;
--分别创建一个本地索引和全局索引
create index partitioned_idx_local on partitioned(id) LOCAL;
create index partitioned_idx_global on partitioned(timestamp) GLOBAL;
--创建一个新表(用于装载分区划出的数据)
create table fy_2004 (timestamp date, id int); create table fy_2005 (timestamp date, id int);
create index fy_2004_idx on fy_2004(id);create index fy_2005_idx on fy_2005(id);
--创建一个新表并插入数据
create table fy_2006 ( timestamp date, id int );
insert into fy_2006
  select to_date('31-12-2006', 'dd-mm-yyyy') - mod(rownum,360),
         object_id
    from all_objects;
create index fy_2006_idx on fy_2006(id) nologging;

create table fy_2007 ( timestamp date, id int );
insert into fy_2007
  select to_date('31-12-2007', 'dd-mm-yyyy') - mod(rownum,360),
         object_id
    from all_objects;
create index fy_2007_idx on fy_2007(id) nologging;

--将分区fy_2004的数据放到表fy_2004中,并删除该分区
alter table partitioned exchange partition fy_2004 with table fy_2004 including indexes without validation;
alter table partitioned drop partition fy_2004;
--创建一个新分区,用于装载新数据
alter table partitioned add partition fy_2006 values less than ( to_date('01-12-2007','dd-mm-yyyy') );
alter table partitioned exchange partition fy_2006 with table fy_2006 including indexes without validation;

--最后查看索引的情况
select index_name, status from user_indexes where table_name='PARTITIONED';
1    PARTITIONED_IDX_LOCAL    N/A
2    PARTITIONED_IDX_GLOBAL    UNUSABLE
--发现全局索引已失效

如果强制用其全局索引,会导致无法查询
set autotrace on explain
select /*+ index( partitioned PARTITIONED_IDX_GLOBAL ) */
 count(*)
  from partitioned
 where timestamp between sysdate - 50 and sysdate;
ORA-01502: 索引 'LTTFM.PARTITIONED_IDX_GLOBAL' 或这类索引的分区处于不可用状态

--直接进行查询,则会进行全表扫描
select count(*) from partitioned where timestamp between sysdate-50 and sysdate;

实验2:全局索引失效的解决办法:
1)可对索引进行重建,
2)直接在进行分区删改的时候 加上更新索引的字句(UPDATE GLOBAL INDEXES):
--删除、交换分区时可加上索引更新的字句,增加一个分区不用进行更新索引,因为新增加的分区空行
alter table partitioned exchange partition fy_2004 with table fy_2004 including indexes without validation UPDATE GLOBAL INDEXES
alter table partitioned drop partition fy_2004 UPDATE GLOBAL INDEXES

注:如果在对分区进行操作时加上了 UPDATE GLOBAL INDEXES 更新索引的字句,那么全局索引就不会失效。

实验3:比较索引重建和更新索引所占用的资源情况:
begin runStats_pkg.rs_start;end;  
alter table partitioned exchange partition fy_2004 with table fy_2004 including indexes without validation;
alter table partitioned drop partition fy_2004;
alter table partitioned add partition fy_2006 values less than (to_date('01-1-2007','dd-mm-yyyy') );
alter table partitioned exchange partition fy_2006 with table fy_2006 including indexes without validation;
alter index partitioned_idx_global rebuild; --采用索引重建的方法
begin  runStats_pkg.rs_middle;end;

alter table partitioned exchange partition fy_2005 with table fy_2005 including indexes without validation update global indexes;
alter table partitioned drop partition fy_2005 update global indexes;
alter table partitioned add partition fy_2007 values less than ( to_date('01-1-2008','dd-mm-yyyy') );
alter table partitioned exchange partition fy_2007 with table fy_2007 including indexes without validation update global indexes;
begin runStats_pkg.rs_stop;end;  --采用索引更新的方法

输出的结果:
Run1 ran in 936 hsecs
Run2 ran in 1101 hsecs
run 1 ran in 85.01% of the time

实验结果:其结果表明对全局索引进行更新要花更长时间。但是如果说系统不允许中断的话,那么还是应该采取索引更新的方法。

分区索引字典

DBA_PART_INDEXES 分区索引的概要统计信息,可以得知每个表上有哪些分区索引,分区索引的类新(local/global,)
Dba_ind_partitions每个分区索引的分区级统计信息
Dba_indexesminusdba_part_indexes,可以得到每个表上有哪些非分区索引

posted on 2011-03-24 11:57  蓝紫  阅读(3243)  评论(0编辑  收藏  举报