局部索引:
局部前缀索引(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,可以得到每个表上有哪些非分区索引