oracle_表分区

系统 2094 0

分区表理论知识

Oracle 提供了分区技术以支持 VLDB(Very Large DataBase) 。分区表通过对分区列的判断,把分区列不同的记录,放到不同的分区中。分区完全对应用透明。

Oracle 的分区表可以包括多个分区, 每个分区都是一个独立的段( SEGMENT ),可以存放到不同的表空间中 。查询时可以通过查询表来访问各个分区中的数据,也可以通过在查询时直接指定分区的方法来进行查询。

 

When to Partition a Table 什么时候需要分区表,官网的 2 个建议如下:

1 Tables greater than 2GB should always be considered for partitioning.

2 Tables containing historical data, in which new data is added into the newest partition. A typical example is a historical table where only the current month's data is updatable and the other 11 months are read only.

 

oracle 10g 中最多支持: 1024k-1 个分区:

Tables can be partitioned into up to 1024K-1 separate partitions

 

联机文档上有关分区表和索引的说明:

Partitioned Tables and Indexes

http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/partconc.htm#sthref2604

 

分区提供以下优点:

1 )由于将数据分散到各个分区中,减少了数据损坏的可能性;

2 )可以对单独的分区进行备份和恢复;

3 )可以将分区映射到不同的物理磁盘上,来分散 IO

4 )提高可管理性、可用性和性能。

 

Oracle 10g 提供了以下几种分区类型:

1 )范围分区( range );

2 )哈希分区( hash );

3 )列表分区( list );

4 )范围-哈希复合分区( range-hash );

5 )范围-列表复合分区( range-list )。

 

Range 分区:

Range 分区是应用范围比较广的表分区方式,它是以列的值的范围来做为分区的划分条件,将记录存放到列值所在的 range 分区中。

如按照时间划分, 2010 1 月的数据放到 a 分区, 2 月的数据放到 b 分区,在创建的时候,需要指定基于的列,以及分区的范围值。

在按时间分区时, 如果某些记录暂无法预测范围,可以创建 maxvalue 分区,所有不在指定范围内的记录都会被存储到 maxvalue 所在分区中。

 

如:

create table pdba (id number, time date) partition by range (time)

(

partition p1 values less than (to_date('2010-10-1', 'yyyy-mm-dd')),

partition p2 values less than (to_date('2010-11-1', 'yyyy-mm-dd')),

partition p3 values less than (to_date('2010-12-1', 'yyyy-mm-dd')),

partition p4 values less than (maxvalue)

)

 

Hash 分区:

  对于那些无法有效划分范围的表,可以使用 hash 分区,这样对于提高性能还是会有一定的帮助。 hash 分区会将表中的数据平均分配到你指定的几个分区中,列所在分区是依据分区列的 hash 值自动分配,因此你并不能控制也不知道哪条记录会被放到哪个分区中, hash 分区也可以支持多个依赖列。

 

如:

create table test

(

transaction_id number primary key,

item_id number(8) not null

)

partition by hash(transaction_id)

(

partition part_01 tablespace tablespace01,

partition part_02 tablespace tablespace02,

partition part_03 tablespace tablespace03

);

在这里,我们指定了每个分区的表空间。

 

List 分区:

List 分区也需要指定列的值,其分区值必须明确指定,该分区列只能有一个,不能像 range 或者 hash 分区那样同时指定多个列做为分区依赖列,但它的单个分区对应值可以是多个。

  在分区时必须确定分区列可能存在的值,一旦插入的列值不在分区范围内,则插入 / 更新就会失败, 因此通常建议使用 list 分区时,要创建一个 default 分区存储那些不在指定范围内的记录 ,类似 range 分区中的 maxvalue 分区。

 

在根据某字段,如城市代码分区时,可以指定 default ,把非分区规则的数据,全部放到这个 default 分区。

 

如:

create table custaddr
(

id varchar2(15 byte) not null,

areacode varchar2(4 byte)
)

partition by list (areacode)
( partition t_list025 values ('025'), 
partition t_list372 values ('372') , 
partition t_list510 values ('510'),

partition p_other values (default)

)

 

组合分区:

如果某表按照某列分区之后,仍然较大,或者是一些其它的需求,还可以通过分区内再建子分区的方式将分区再分区,即组合分区的方式。

  组合分区呢在 10g 中有两种: range-hash range-list 。注意顺序,根分区只能是 range 分区,子分区可以是 hash 分区或 list 分区。

 

如:

create table test

(

transaction_id number primary key,

transaction_date date

)

partition by range(transaction_date) subpartition by hash(transaction_id)

subpartitions 3 store in (tablespace01,tablespace02,tablespace03)

(

partition part_01 values less than(to_date(’2009-01-01’,’yyyy-mm-dd’)),

partition part_02 values less than(to_date(’2010-01-01’,’yyyy-mm-dd’)),

partition part_03 values less than(maxvalue)

);

 

create table emp_sub_template (deptno number, empname varchar(32), grade number)

partition by range(deptno) subpartition by hash(empname)

subpartition template

(subpartition a tablespace ts1,

subpartition b tablespace ts2,

subpartition c tablespace ts3,

subpartition d tablespace ts4

)

(partition p1 values less than (1000),

partition p2 values less than (2000),

partition p3 values less than (maxvalue)

);

 

 

create table quarterly_regional_sales

(deptno number, item_no varchar2(20),

txn_date date, txn_amount number, state varchar2(2))

tablespace ts4

partition by range (txn_date)

subpartition by list (state)

(partition q1_1999 values less than (to_date('1-apr-1999','dd-mon-yyyy'))

(subpartition q1_1999_northwest values ('or', 'wa'),

subpartition q1_1999_southwest values ('az', 'ut', 'nm'),

subpartition q1_1999_northeast values ('ny', 'vm', 'nj'),

subpartition q1_1999_southeast values ('fl', 'ga'),

subpartition q1_1999_northcentral values ('sd', 'wi'),

subpartition q1_1999_southcentral values ('ok', 'tx')

),

partition q2_1999 values less than ( to_date('1-jul-1999','dd-mon-yyyy'))

(subpartition q2_1999_northwest values ('or', 'wa'),

subpartition q2_1999_southwest values ('az', 'ut', 'nm'),

subpartition q2_1999_northeast values ('ny', 'vm', 'nj'),

subpartition q2_1999_southeast values ('fl', 'ga'),

subpartition q2_1999_northcentral values ('sd', 'wi'),

subpartition q2_1999_southcentral values ('ok', 'tx')

),

partition q3_1999 values less than (to_date('1-oct-1999','dd-mon-yyyy'))

(subpartition q3_1999_northwest values ('or', 'wa'),

subpartition q3_1999_southwest values ('az', 'ut', 'nm'),

subpartition q3_1999_northeast values ('ny', 'vm', 'nj'),

subpartition q3_1999_southeast values ('fl', 'ga'),

subpartition q3_1999_northcentral values ('sd', 'wi'),

subpartition q3_1999_southcentral values ('ok', 'tx')

),

partition q4_1999 values less than ( to_date('1-jan-2000','dd-mon-yyyy'))

(subpartition q4_1999_northwest values ('or', 'wa'),

subpartition q4_1999_southwest values ('az', 'ut', 'nm'),

subpartition q4_1999_northeast values ('ny', 'vm', 'nj'),

subpartition q4_1999_southeast values ('fl', 'ga'),

subpartition q4_1999_northcentral values ('sd', 'wi'),

subpartition q4_1999_southcentral values ('ok', 'tx')

)

);

 

 

Oracle 11g 中,组合分区功能这块有所增强,又增加了 range-range,list-range,

list-list,list-hash ,并且   11g 里面还支持 Interval 分区和虚拟列分区。

 

这块可以参考 Blog

Oracle 11g  新特性简介

http://blog.csdn.net/tianlesoftware/archive/2010/01/06/5134819.aspx

 

分区表    Interval 分区     虚拟列   按星期分区表

http://blog.csdn.net/tianlesoftware/archive/2010/06/10/5662337.aspx

 

 

普通表转分区表方法

 

将普通表转换成分区表有 4 种方法:

1. Export/import method

2. Insert with a subquery method

3. Partition exchange method

4. DBMS_REDEFINITION

具体参考:

How to Partition a Non-partitioned Table [ID 1070693.6]

http://blog.csdn.net/tianlesoftware/archive/2011/03/02/6218704.aspx

 

逻辑导出导入这里就不做说明,我们看看其他三种方法。

 

2.1  插入:   Insert with a subquery method

这种方法就是使用 insert  来实现。   当然在创建分区表的时候可以一起插入数据,也可以创建好后在 insert  进去。   这种方法采用 DDL 语句,不产生 UNDO ,只产生少量 REDO ,建表完成后数据已经在分布到各个分区中。

 

SQL> select count(*) from dba;

COUNT(*)

----------

2713235

SQL> alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss';

会话已更改。

 

SQL> select time_fee from dba where rownum<5;

TIME_FEE

-------------------

2011-02-17 19:29:09

2011-02-17 19:29:15

2011-02-17 19:29:18

2011-02-17 19:29:20

SQL>

 

2.1.1 Oracle 11g Interval

11g 里的 Interval 创建,这种方法对没有写全的分区会自动创建。 比如我这里只写了 1 月日期,如果插入的数据有其他月份的,会自动生成对应的分区。

 

/* Formatted on 2011/03/02 15:41:09 (QP5 v5.115.810.9015) */

CREATE   TABLE  intervaldave

PARTITION   BY   RANGE   ( time_fee )

INTERVAL   (   NUMTOYMINTERVAL   ( 1 ,   'MONTH' )   )

(PARTITION  part1

VALUES   LESS   THAN   (TO_DATE   ( '01/12/2010' ,   'MM/DD/YYYY' )))

AS

SELECT   ID,  TIME_FEE  FROM   DAVE ;

 

 

SQL> select table_name,partition_name from user_tab_partitions where table_name='INTERVALDAVE';

 

TABLE_NAME PARTITION_NAME

------------------------------ ------------------------------

INTERVALDAVE PART1

INTERVALDAVE SYS_P24

INTERVALDAVE SYS_P25

INTERVALDAVE SYS_P26

INTERVALDAVE SYS_P33

INTERVALDAVE SYS_P27

INTERVALDAVE SYS_P28

 

2.1.2 Oracle 10g  版本

10g 里面,我需要写全所有的分区。

 

sql> create table pdba (id, time) partition by range (time)

2 (partition p1 values less than (to_date('2010-10-1', 'yyyy-mm-dd')),

3 partition p2 values less than (to_date('2010-11-1', 'yyyy-mm-dd')),

4 partition p3 values less than (to_date('2010-12-1', 'yyyy-mm-dd')),

5 partition p4 values less than (maxvalue))

6 as select id, time_fee from dba;

表已创建。

SQL> select table_name,partition_name from user_tab_partitions where table_name='PDBA';

TABLE_NAME PARTITION_NAME

------------------------------ ------------------------------

PDBA P1

PDBA P2

PDBA P3

PDBA P4

sql> select count(*) from pdba partition (p1);

count(*)

----------

1718285

sql> select count(*) from pdba partition (p2);

count(*)

----------

183667

sql> select count(*) from pdba partition (p3);

count(*)

----------

188701

sql> select count(*) from pdba partition (p4);

count(*)

----------

622582

sql>

 

现在分区表已经建好了,但是表名不一样,需要用 rename 对表重命名一下:

SQL> rename dba to dba_old;

表已重命名。

SQL> rename pdba to dba;

表已重命名。

SQL> select table_name,partition_name from user_tab_partitions where table_name='DBA';

TABLE_NAME PARTITION_NAME

------------------------------ ------------------------------

DBA P1

DBA P2

DBA P3

DBA P4

 

2.2 .  交换分区: Partition exchange method

这种方法只是对数据字典中分区和表的定义进行了修改,没有数据的修改或复制,效率最高。适用于包含大数据量的表转到分区表中的一个分区的操作。尽量在闲时进行操作。

 

交换分区的操作步骤如下:

1.  创建分区表,假设有 2 个分区, P1 P2.

2.  创建表 A 存放 P1 规则的数据。

3.  创建表 存放 P2 规则的数据。

4.  用表 P1  分区交换。   把表 A 的数据放到到 P1 分区

5.  用表 p2  分区交换。   把表 B 的数据存放到 P2 分区。

 

创建分区表:

sql> create table p_dba

2 (id number,time date)

3 partition by range(time)

4 (

5 partition p1 values less than (to_date('2010-09-1', 'yyyy-mm-dd')),

6 partition p2 values less than (to_date('2010-11-1', 'yyyy-mm-dd'))

7 );

表已创建。

 

注意:我这里只创建了 2 个分区,没有创建存放其他数据的分区。

 

创建 2 个分别对应分区的基表:

SQL> CREATE TABLE dba_p1 as SELECT id,time_fee FROM dba_old  WHERE time_fee<TO_DATE('2010-09-1', 'YYYY-MM-DD');

表已创建。

 

SQL> CREATE TABLE dba_p2 as SELECT id,time_fee FROM dba_old  WHERE time_fee<TO_DATE('2010-11-1', 'YYYY-MM-DD') and time_fee>TO_DATE('2010-09-1', 'YYYY-MM-DD');

表已创建。

 

SQL> select count(*) from dba_p1;

COUNT(*)

----------

1536020

SQL> select count(*) from dba_p2;

COUNT(*)

----------

365932

 

SQL>

 

2 个基表与 2 个分区进行交换:

SQL> alter table p_dba exchange partition p1 with table dba_p1;

表已更改。

SQL> alter table p_dba exchange partition p2 with table dba_p2;

表已更改。

 

查询 2 个分区:

SQL> select count(*) from p_dba partition(p1);

COUNT(*)

----------

1536020

SQL> select count(*) from p_dba partition(p2);

COUNT(*)

----------

365932

注意:数据和之前的基表一致。

 

查询原来的 2 个基表:

SQL> select count(*) from dba_p2;

COUNT(*)

----------

0

SQL> select count(*) from dba_p1;

COUNT(*)

----------

0

注意:  2 个基表的数据变成成 0

 

在这里我们看一个问题,一般情况下,我们在创建分区表的时候,都会有一个其他分区,用来存放不匹配分区规则的数据。 在这个例子中,我只创建了 2 个分区,没有创建 maxvalue 分区。 现在我来插入一条不满足规则的数据,看结果:

 

SQL> insert into p_dba values(999999,to_date(' 2012-12-29 ','yyyy-mm-dd'));

insert into p_dba values(999999,to_date('2012-12-29','yyyy-mm-dd'))

*

 1  行出现错误 :

ORA-14400:  插入的分区关键字未映射到任何分区

SQL> insert into p_dba values(999999,to_date('2009-12-29','yyyy-mm-dd'));

已创建  1  行。

SQL> select * from p_dba where id=999999;

 

ID TIME

---------- --------------

999999 29-12 -09

SQL> alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss';

会话已更改。

SQL> select * from p_dba where id=999999;

 

ID TIME

---------- -------------------

999999 2009-12-29 00:00:00

SQL>

 

通过这个测试可以清楚,如果插入的数据不满足分区规则,会报 ORA-14400 错误。

 

2.3 .  使用在线重定义: DBMS_REDEFINITION

 

在线重定义能保证数据的一致性,在大部分时间内,表都可以正常进行 DML 操作。 只在切换的瞬间锁表,具有很高的可用性 。这种方法具有很强的灵活性,对各种不同的需要都能满足。而且,可以在切换前进行相应的授权并建立各种约束,可以做到切换完成后不再需要任何额外的管理操作。

 

关于 DBMS_REDEFINITION 的介绍,参考官方连接:

http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14258/d_redefi.htm#CBBFDJBC

 

关于用在线重定义创建分区表,参考:

How To Partition Existing Table Using DBMS_Redefinition [ID 472449.1]

http://blog.csdn.net/tianlesoftware/archive/2011/03/02/6218693.aspx

 

这个功能只在 9.2.0.4 以后的版本才有,在线重定义表具有以下功能:

1 )修改表的存储参数;

2 )将表转移到其他表空间;

3 )增加并行查询选项;

4 )增加或删除分区;

5 )重建表以减少碎片;

6 )将堆表改为索引组织表或相反的操作;

7 )增加或删除一个列。

 

使用在线重定义的一些限制条件:

1  There must be enough space to hold two copies of the table.

2  Primary key columns cannot be modified.

3 )  Tables must have primary keys.

4  Redefinition must be done within the same schema.

5  New columns added cannot be made NOT NULL until after the redefinition operation.

6  Tables cannot contain LONGs, BFILEs or User Defined Types.

7  Clustered tables cannot be redefined.

8 )  Tables in the SYS or SYSTEM schema cannot be redefined.

9  Tables with materialized view logs or materialized views defined on them cannot be redefined.

10  Horizontal sub setting of data cannot be performed during the redefinition.

 

Oracle 10.2.0.4 11.1.0.7  版本下,在线重定义可能会遇到如下 bug

Bug 7007594 - ORA-600 [12261]

http://blog.csdn.net/tianlesoftware/archive/2011/03/02/6218681.aspx

 

在线重定义的大致操作流程如下:

1 )创建基础表 A ,如果存在,就不需要操作。

2 )创建临时的分区表 B

3 )开始重定义,将基表 A 的数据导入临时分区表 B

4 )结束重定义,此时在 DB 的  Name Directory 里,已经将 2 个表进行了交换。即此时基表 A 成了分区表,我们创建的临时分区表 成了普通表。 此时我们可以删除我们创建的临时表 B 。它已经是普通表。

 

 

下面看一个示例:

 

1.  创建基本表和索引

sql> conn icd/icd;

已连接。

sql> create table unpar_table (

2 id number(10) primary key,

3 create_date date

4 );

表已创建。

sql> insert into unpar_table select rownum, created from dba_objects;

已创建 72288 行。

sql> create index create_date_ind on unpar_table(create_date);

索引已创建。

sql> commit;

提交完成。

 

2.  收集表的统计信息

sql> exec dbms_stats.gather_table_stats('icd', 'unpar_table', cascade => true);

pl/sql  过程已成功完成。

 

3.  创建临时分区表

sql> create table par_table (id number primary key, time date) partition by range (time)

2 (partition p1 values less than (to_date('2004-7-1', 'yyyy-mm-dd')),

3 partition p2 values less than (to_date('2005-1-1', 'yyyy-mm-dd')),

4 partition p3 values less than (to_date('2005-7-1', 'yyyy-mm-dd')),

5 partition p4 values less than (maxvalue));

表已创建。

 

4.  进行重定义操作

 

4.1  检查重定义的合理性

sql> exec dbms_redefinition.can_redef_table('icd', 'unpar_table');

pl/sql  过程已成功完成。

 

4.2  如果 4.1  没有问题,开始重定义,这个过程可能要等一会。

 

这里要注意:如果分区表和原表列名相同,可以用如下方式进行:

SQL> BEGIN

DBMS_REDEFINITION.start_redef_table(

uname => 'ICD',

orig_table => 'unpar_table',

int_table => 'par_table');

END;

/

如果分区表的列名和原表不一致,那么在开始重定义的时候,需要重新指定映射关系:

SQL> EXEC DBMS_REDEFINITION.START_REDEF_TABLE(

'ICD',

'unpar_table',

'par_table',

'ID ID, create_date TIME', --  在这里指定新的映射关系

DBMS_REDEFINITION.CONS_USE_PK);

 

这一步操作结束后,数据就已经同步到这个临时的分区表里来了。

 

4.3  同步新表,这是可选的操作

SQL> BEGIN

2 dbms_redefinition.sync_interim_table(

3 uname => 'ICD',

4 orig_table => 'unpar_table',

5 int_table => 'par_table');

6 END;

7 /

PL/SQL  过程已成功完成。

 

4.4  创建索引,在线重定义只重定义数据,索引还需要单独建立。

sql> create index create_date_ind2 on par_table(time);

索引已创建。

 

4.5  收集新表的统计信息

sql> exec dbms_stats.gather_table_stats('icd', 'par_table', cascade => true);

pl/sql  过程已成功完成。

 

4.6  结束重定义

SQL> BEGIN

2 dbms_redefinition.finish_redef_table(

3 uname => 'ICD',

4 orig_table => 'unpar_table',

5 int_table => 'par_table');

6 END;

7 /

PL/SQL  过程已成功完成。

 

结束重定义的意义:

基表 unpar_table  和临时分区表 par_table  进行了交换。 此时临时分区表 par_table 成了普通表,我们的基表 unpar_table 成了分区表。

 

我们在重定义的时候,基表 unpar_table 是可以进行 DML 操作的。 只有在 2 个表进行切换的时候会有短暂的锁表。

 

5.  删除临时表

SQL> DROP TABLE par_table;

表已删除。

 

6.  索引重命名

SQL> ALTER INDEX create_date_ind2 RENAME TO create_date_ind;

索引已更改。

 

7.  验证

sql> select partitioned from user_tables where table_name = 'UNPAR_TABLE';

par

---

yes

sql> select partition_name from user_tab_partitions where table_name = 'UNPAR_TABLE';

partition_name

------------------------------

p1

p2

p3

p4

sql> select count(*) from unpar_table;

count(*)

----------

72288

sql> select count(*) from unpar_table partition (p4);

count(*)

----------

72288

sql>

 

 

分区表的其他操作

3.1  添加新的分区

添加新的分区有 2 中情况:

1 )原分区里边界是 maxvalue 或者 default 。 这种情况下,我们需要把边界分区 drop 掉,加上新分区后,在添加上新的分区。或者采用 split ,对边界分区进行拆分。

2 )没有边界分区的。 这种情况下,直接添加分区就可以了。

 

以边界分区添加新分区示例:

1 )分区表和索引的信息如下:

SQL> create table custaddr

2 (

3 id varchar2(15 byte) not null,

4 areacode varchar2(4 byte)

5 )

6 partition by list (areacode)

7 (

8 partition t_list556 values ('556') tablespace icd_service,

9 partition p_other values (default)tablespace icd_service

10 );

表已创建。

SQL> create index ix_custaddr_id on custaddr(id)

2 local (

3 partition t_list556 tablespace icd_service,

4 partition p_other tablespace icd_service

5 );

索引已创建。

 

2 )插入几条测试数据:

SQL> insert into custaddr values('1','556');

已创建  1  行。

SQL> insert into custaddr values('2','551');

已创建  1  行。

SQL> insert into custaddr values('3','555');

已创建  1  行。

SQL> commit;

提交完成。

SQL> select * from custaddr;

ID AREA

--------------- ----

1 556

2 551

3 555

SQL> select * from custaddr partition(t_list556);

ID AREA

--------------- ----

1 556

SQL>

 

3 )删除 default 分区

sql> alter table custaddr drop partition p_other;

表已更改。

sql> select table_name,partition_name from user_tab_partitions where table_name='CUSTADDR';

table_name partition_name

------------------------------ ------------------------------

custaddr t_list556

 

4 )添加新分区

SQL> alter table custaddr add partition t_list551 values('551') tablespace icd_service;

表已更改。

SQL> select table_name,partition_name from user_tab_partitions where table_name='CUSTADDR';

 

TABLE_NAME PARTITION_NAME

------------------------------ ------------------------------

CUSTADDR T_LIST556

CUSTADDR T_LIST551

5 )添加 default  分区

SQL> alter table custaddr add partition p_other values (default) tablespace icd_service;

表已更改。

SQL> select table_name,partition_name from user_tab_partitions where table_name='CUSTADDR';

 

TABLE_NAME PARTITION_NAME

------------------------------ ------------------------------

CUSTADDR T_LIST556

CUSTADDR T_LIST551

CUSTADDR P_OTHER

 

6 )对于局部索引, oracle 会自动增加一个局部分区索引。验证一下:

sql> select owner,index_name,table_name,partitioning_type from  dba_part_indexes  where index_name='ix_custaddr_id';

owner index_name table_name

---------------------- ------------------------------ ------------------

icd ix_custaddr_id custaddr

sql> select index_owner,index_name,partition_name from  dba_ind_partitions  where index_name='ix_custaddr_id';

index_owner index_name partition_name

------------------------------ ------------------------------ ------------------

icd ix_custaddr_id p_other

icd ix_custaddr_id t_list551

icd ix_custaddr_id t_list556

 

分区索引自动创建了。

 

3.2 split  分区拆分

3.1  中,我们说明了可以使用 split 的方式来添加分区。 这里我们用 split 方法继续上面的实验。

 

sql> alter table custaddr split partition p_other  values('552')  into (partition t_list552 tablespace icd_service, partition p_other tablespace icd_service);

表已更改。

-- 注意这里红色的地方,如果是 Range 类型的,使用 at List 使用 Values

SQL> select table_name,partition_name from user_tab_partitions where table_name='CUSTADDR';

TABLE_NAME PARTITION_NAME

------------------------------ ------------------------------

CUSTADDR T_LIST556

CUSTADDR T_LIST551

CUSTADDR T_LIST552

CUSTADDR P_OTHER

 

SQL> select index_owner,index_name,partition_name from dba_ind_partitions where index_name='IX_CUSTADDR_ID';

 

index_owner index_name partition_name

------------------------------ ------------------------------ ------------------

icd ix_custaddr_id p_other

icd ix_custaddr_id t_list551

icd ix_custaddr_id t_list552

icd ix_custaddr_id t_list556

 

注意:分区表会自动维护局部分区索引。全局索引会失效,需要进行 rebuild

 

3.3  合并分区 Merge

相邻的分区可以 merge 为一个分区,新分区的下边界为原来边界值较低的分区,上边界为原来边界值较高的分区, 原先的局部索引相应也会合并,全局索引会失效,需要 rebuild

 

SQL>  alter table custaddr merge partitions t_list552,p_other into partition p_other;

表已更改。

SQL> select index_owner,index_name,partition_name from dba_ind_partitions where index_name='IX_CUSTADDR_ID';

index_owner index_name partition_name

-------------------- ------------------------------ ------------------

icd ix_custaddr_id p_other

icd ix_custaddr_id t_list551

icd ix_custaddr_id t_list556

 

SQL> select table_name,partition_name from user_tab_partitions where table_name='CUSTADDR';

 

table_name partition_name

------------------------------ ------------------------------

custaddr t_list556

custaddr t_list551

custaddr p_other

 

3.4 .  移动分区

SQL> alter table custaddr move partition P_OTHER tablespace system;

 

表已更改。

SQL> alter table custaddr move partition P_OTHER tablespace icd_service;

表已更改。

 

注意: 分区移动会自动维护局部分区索引, oracle 不会自动维护全局索引,所以需要我们重新 rebuild 分区索引,具体需要 rebuild 哪些索引,可以通过 dba_part_indexes,dba_ind_partitions 去判断。

 

SQL> Select index_name,status From  user_indexes  Where table_name='CUSTADDR';

 

INDEX_NAME STATUS

------------------------------ --------

IX_CUSTADDR_ID N/A

 

 

3.5. Truncate 分区

SQL> select * from custaddr partition(T_LIST556);

ID AREA

--------------- ----

1 556

SQL> alter table custaddr truncate partition(T_LIST556);

表被截断。

SQL> select * from custaddr partition(T_LIST556);

未选定行

 

说明:

Truncate 相对 delete 操作很快,数据仓库中的大量数据的批量数据加载可能会有用到; 截断分区同样会自动维护局部分区索引,同时会使全局索引 unusable, 需要重建

 

3.6. Drop 分区

SQL> alter table custaddr drop partition T_LIST551;

表已更改。

 

SQL> select table_name,partition_name from user_tab_partitions where table_name='CUSTADDR';

TABLE_NAME PARTITION_NAME

------------------------------ ------------------------------

CUSTADDR T_LIST556

CUSTADDR P_OTHER

同样会自动维护局部分区索引,同时会使全局索引 unusable, 需要重建

 

 

分区表的索引

分区索引分为本地 (local index) 索引和全局索引 (global index) 局部索引比全局索引容易管理 而全局索引比较快。

 

与索引有关的表:

dba_part_indexes  分区索引的概要统计信息,可以得知每个表上有哪些分区索引,分区索引的类型 (local/global)

dba_ind_partitions  每个分区索引的分区级统计信息

dba_indexes/dba_part_indexes  可以得到每个表上有哪些非分区索引

 

Local 索引肯定是分区索引, Global 索引可以选择是否分区,如果分区,只能是有前缀的分区索引。

 

分区索引 2 类: 有前缀 (prefix) 的分区索引 无前缀 (nonprefix) 的分区索引 :

1 )有前缀的分区索引指包含了 分区键 ,并且将其作为引导列的索引。

如:

create index i_id_global on  PDBA(id)  global  -- 引导列

2 partition by  range(id) -- 分区键

3 (partition p1 values less than (200),

4 partition p2 values less than (maxvalue)

5 );

这里的 ID  就是分区键,并且分区键 id  也是索引的引导列。

 

2 )无前缀的分区索引的 不是以分区键开头, 或者不包含分区键列

如:

create index ix_custaddr_local_id_p on custaddr(id)

local (

partition t_list556 tablespace icd_service,

partition p_other tablespace icd_service

)

 

这个分区是按照 areacode 来的。但是索引的引导列是 ID 。 所以它就是非前缀分区索引。

 

全局分区索引不支持非前缀的分区索引,如果创建,报错如下:

SQL> create index i_time_global on  PDBA(id)  global  -- 索引引导列

2 partition by range (time) -- 分区建

3 (partition p1 values less than (TO_DATE('2010-12-1', 'YYYY-MM-DD')),

4 partition p2 values less than (maxvalue)

5 );

partition by range(time)

*

 2  行出现错误 :

ORA-14038: GLOBAL  分区索引必须加上前缀

 

 

4.1 Local  本地索引

对于 local 索引,当表的分区发生变化时,索引的维护由 Oracle 自动进行。

 

注意事项:

1 )  局部索引一定是分区索引 分区键等同于表的分区键

2   前缀和非前缀索引都可以支持索引分区消除,前提是查询的条件中包含索引分区键。

3 ) 局部索引只支持分区内的唯一性,无法支持表上的唯一性,因此如果要用局部索引去给表做唯一性约束,则约束中必须要包括分区键列。

4 ) 局部分区索引是对单个分区的,每个分区索引只指向一个表分区;全局索引则不然,一个分区索引能指向 n 个表分区,同时,一个表分区,也可能指向 n 个索引分区,对分区表中的某个分区做 truncate 或者 move shrink 等,可能会影响到 n 个全局索引分区,正因为这点,局部分区索引具有更高的可用性。

5 ) 位图索引必须是局部分区索引。

6 ) 局部索引多应用于数据仓库环境中。

7  B 树索引和位图索引都可以分区,但是 HASH 索引不可以被分区。

 

 

示例:

sql> create index ix_custaddr_local_id on custaddr(id) local;

索引已创建。

 

和下面 SQL  效果相同,因为 local 索引就是分区索引:

create index ix_custaddr_local_id_p on custaddr(id)

local (

partition t_list556 tablespace icd_service,

partition p_other tablespace icd_service

)

 

SQL> create index ix_custaddr_local_areacode on custaddr(areacode) local;

索引已创建。

 

验证 2 个索引的类型:

SQL> select index_name,table_name,partitioning_type,locality,ALIGNMENT from user_part_indexes where table_name='CUSTADDR';

 

index_name table_name partition locali alignment

------------------------------ ---------- --------- ------ ------------

ix_custaddr_local_areacode custaddr list local prefixed

ix_custaddr_local_id custaddr list local non_prefixed

 

因为我们的 custaddr 表是按 areacode 进行分区的,所以索引 ix_custaddr_local_areacode 是有前缀的索引( prefixed )。而 ix_custaddr_local_id 是非前缀索引。

 

4.2 Global 索引

对于 global 索引,可以选择是否分区,而且索引的分区可以不与表分区相对应。 全局分区索引只能是 B 树索引 ,到目前为止 (10gR2) oracle 只支持有前缀的全局索引。

另外 oracle 不会自动的维护全局分区索引,当我们在对表的分区做修改之后,如果对分区进行维护操作时不加上 update global indexes 的话,通常会导致全局索引的 INVALDED ,必须在执行完操作后  REBUILD

 

注意事项:

1 )全局索引可以分区,也可以是不分区索引, 全局索引必须是前缀索引 ,即全局索引的索引列必须是以索引分区键作为其前几列。

2 )全局索引可以依附于分区表;也可以依附于非分区表。

3 )全局分区索引的索引条目可能指向若干个分区,因此,对于全局分区索引,即使只截断一个分区中的数据,都需要 rebulid 若干个分区甚至是整个索引。

4 )全局索引多应用于 oltp 系统中。

5 )全局分区索引只按范围或者散列分区, hash 分区是 10g 以后才支持。

6 )  oracle9i 以后对分区表做 move 或者 truncate 的时可以用 update global indexes 语句来同步更新全局分区索引,用消耗一定资源来换取高度的可用性。

7 ) 表用 a 列作分区,索引用 b 做局部分区索引,若 where 条件中用 b 来查询,那么 oracle 会扫描所有的表和索引的分区,成本会比分区更高,此时可以考虑用 b 做全局分区索引。

 

 

注意: Oracle 只支持 2 中类型的全局分区索引:

range partitioned   Hash Partitioned.

 

官网的说明如下:

Global Partitioned Indexes

Oracle offers two types of global partitioned index: range partitioned and hash partitioned.

1 Global Range Partitioned Indexes

Global range partitioned indexes are flexible in that the degree of partitioning and the partitioning key are independent from the table's partitioning method. They are commonly used for OLTP environments and offer efficient access to any individual record.

The highest partition of a global index must have a partition bound, all of whose values are MAXVALUE. This ensures that all rows in the underlying table can be represented in the index. Global prefixed indexes can be unique or nonunique.

You cannot add a partition to a global index because the highest partition always has a partition bound of MAXVALUE.  If you wish to add a new highest partition, use the ALTER INDEX SPLIT PARTITION statement. If a global index partition is empty, you can explicitly drop it by issuing the ALTER INDEX DROP PARTITION statement . If a global index partition contains data, dropping the partition causes the next highest partition to be marked unusable. You cannot drop the highest partition in a global index.

2 Global Hash Partitioned Indexes

Global hash partitioned indexes improve performance by spreading out contention when the index is monotonically growing. In other words, most of the index insertions occur only on the right edge of an index.

 

3 Maintenance of Global Partitioned Indexes

By default, the following operations on partitions on a heap-organized table mark all global indexes as unusable:

ADD (HASH)

COALESCE (HASH)

DROP

EXCHANGE

MERGE

MOVE

SPLIT

TRUNCATE

 

示例 全局索引,全局索引对所有分区类型都支持:

sql> create index ix_custaddr_ global_id on custaddr(id) global;

索引已创建。

 

示例 2 :全局分区索引,只支持 Range  分区和 Hash  分区:

 

1 )创建 2 个测试分区表:

sql> create table pdba (id number, time date)  partition by range (time)

2 (

3 partition p1 values less than (to_date('2010-10-1', 'yyyy-mm-dd')),

4 partition p2 values less than (to_date('2010-11-1', 'yyyy-mm-dd')),

5 partition p3 values less than (to_date('2010-12-1', 'yyyy-mm-dd')),

6 partition p4 values less than (maxvalue)

7 );

表已创建。

 

SQL> create table Thash

2 (

3 id number primary key,

4 item_id number(8) not null

5 )

partition by hash(id)

7 (

8 partition part_01,

9 partition part_02,

10 partition part_03

11 );

 

表已创建。

 

2 )创建分区索引

 

示例 2 :全局分区索引

 

SQL> create index i_id_global on  PDBA(id)  global

2 partition by  range(id)

3 (partition p1 values less than (200),

4 partition p2 values less than (maxvalue)

5 );

索引已创建。

-- 这个是有前缀的分区索引。

 

SQL> create index i_time_global on  PDBA(id)  global

2 partition by range (time)

3 (partition p1 values less than (TO_DATE('2010-12-1', 'YYYY-MM-DD')),

4 partition p2 values less than (maxvalue)

5 );

partition by range(time)

*

 2  行出现错误 :

ORA-14038: GLOBAL  分区索引必须加上前缀

 

 

SQL> create index i_time_global on  PDBA(time)  global

2 partition by  range(time)

3 (partition p1 values less than (TO_DATE('2010-12-1', 'YYYY-MM-DD')),

4 partition p2 values less than (maxvalue)

5 );

索引已创建。

-- 有前缀的分区索引

 

SQL> select index_name,table_name,partitioning_type,locality,ALIGNMENT from user_part_indexes where table_name='PDBA';

index_name table_name partition locali alignment

------------------------------ ---------- --------- ------ ------------

i_id_global pdba range global  prefixed

i_time_global pdba range global  prefixed

 

SQL> CREATE INDEX ix_hash ON  PDBA (id,time)  GLOBAL

2 PARTITION BY  HASH (id)

3 (PARTITION p1,

4 PARTITION p2,

5 PARTITION p3,

6 PARTITION p4);

索引已创建。

 

只要索引的引导列包含分区键,就是有前缀的分区索引。

 

 

4.3  索引重建问题

 

1 )分区索引

对于分区索引,不能整体进行重建,只能对单个分区进行重建。语法如下:

Alter index idx_name rebuild partition index_partition_name [online nologging]

说明:

online: 表示重建的时候不会锁表。

nologging :表示建立索引的时候不生成日志,加快速度。

 

如果要重建分区索引,只能 drop 表原索引,在重新创建:

SQL>create index loc_xxxx_col on xxxx(col) local tablespace SYSTEM;

这个操作要求较大的临时表空间和排序区。

 

示例:

SQL> select index_name,partition_name from user_ind_partitions where index_name='I_TIME_GLOBAL';

INDEX_NAME PARTITION_NAME

------------------------------ ------------------------------

I_TIME_GLOBAL P1

I_TIME_GLOBAL P2

 

SQL> alter index I_TIME_GLOBAL rebuild partition p1 online nologging;

索引已更改。

SQL> alter index I_TIME_GLOBAL rebuild partition p2 online nologging;

索引已更改。

 

2 )全局索引

Oracle  会自动维护分区索引,对于全局索引,如果在对分区表操作时,没有指定 update index ,则会导致全局索引失效,需要重建。

 

SQL> select owner,index_name,table_name,status from dba_indexes where INDEX_NAME='IX_PDBA_GLOBAL';

 

owner index_name table_name status

------------------------------ ------------------------------ ---------- -------

sys ix_pdba_global pdba valid

 

删除一个分区:

SQL> alter table pdba drop partition p2;

表已更改。

 

SQL> select owner,index_name,table_name,status from dba_indexes where INDEX_NAME='IX_PDBA_GLOBAL';

owner index_name table_name status

------------------------------ ------------------------------ ---------- -------

sys ix_pdba_global pdba  valid

 

split  分区:

SQL> alter table pdba split partition P4 at(TO_DATE('2010-12-21 00:00:00','YYYY-MM-DD HH24:MI:SS')) into (partition P4, partition P5);

表已更改。

 

SQL> select owner,index_name,table_name,status from dba_indexes where INDEX_NAME='IX_PDBA_GLOBAL';

owner index_name table_name status

------------------------------ ------------------------------ ---------- -------

sys ix_pdba_global pdba  valid

 

drop  分区时使用 update indexes

SQL> alter table pdba drop partition P4 UPDATE INDEXES;

表已更改。

SQL> select owner,index_name,table_name,status from dba_indexes where INDEX_NAME='IX_PDBA_GLOBAL';

owner index_name table_name status

---------------------- ------------------------------ ---------- -------

sys ix_pdba_global pdba valid

 

 

做了几个 drop 分区操作,全局索引没有失效,有点奇怪。 不过如果在生产环境中,还是小心点。

 

重建全局索引命令如下:

Alter index idx_name rebuild [online nologging]

示例:

SQL> Alter index ix_pdba_global rebuild online nologging;

索引已更改。

 

 

 

补充一点,分区表存储空间的问题:

SQL> select table_name,partition_name,tablespace_name from user_tab_partitions where table_name='DBA';

TABLE_NAME PARTITION_NAME TABLESPACE_NAME
---------- ------------------------------ ------------------------------
DBA P1 SYSTEM
DBA P2 SYSTEM
DBA P3 SYSTEM
DBA P4 SYSTEM

通过 user_tab_partitions  表可以查看到每个分区对应的 tablesapce_name.  但是,如果通过 all_tables  表,却查不到分区表对应表空间的信息。


分区表:
SQL> select owner,table_name,tablespace_name,cluster_name from all_tables where table_name='DBA';

OWNER TABLE_NAME TABLESPACE_NAME CLUSTER_NAME
----- ---------- ------------------------------ -----------------------------------------------------
SYS DBA

普通表:
SQL> select owner,table_name,tablespace_name,cluster_name from all_tables where table_name='DAVE';

OWNER TABLE_NAME TABLESPACE_NAME CLUSTER_NAME
----- ---------- ------------------------------ ---------------------------------------------------
SYS DAVE SYSTEM

 

(5).有关表分区的一些维护性操作: 
一、添加分区 
以下代码给SALES表添加了一个P3分区
ALTER TABLE SALES ADD PARTITION P3 VALUES LESS THAN(TO_DATE('2003-06-01','YYYY-MM-DD'));
注意:以上添加的分区界限应该高于最后一个分区界限。
以下代码给SALES表的P3分区添加了一个P3SUB1子分区
ALTER TABLE SALES MODIFY PARTITION P3 ADD SUBPARTITION P3SUB1 VALUES('COMPLETE');
二、删除分区 
以下代码删除了P3表分区:
ALTER TABLE SALES DROP PARTITION P3;
在以下代码删除了P4SUB1子分区:
ALTER TABLE SALES DROP SUBPARTITION P4SUB1;
注意:如果删除的分区是表中唯一的分区,那么此分区将不能被删除,要想删除此分区,必须删除表。
三、截断分区 
截断某个分区是指删除某个分区中的数据,并不会删除分区,也不会删除其它分区中的数据。当表中即使只有一个分区时,也可以截断该分区。通过以下代码截断分区:
ALTER TABLE SALES TRUNCATE PARTITION P2;
通过以下代码截断子分区:
ALTER TABLE SALES TRUNCATE SUBPARTITION P2SUB2;
四、合并分区 
合并分区是将相邻的分区合并成一个分区,结果分区将采用较高分区的界限,值得注意的是,不能将分区合并到界限较低的分区。以下代码实现了P1 P2分区的合并:
ALTER TABLE SALES MERGE PARTITIONS P1,P2 INTO PARTITION P2;
五、拆分分区 
拆分分区将一个分区拆分两个新分区,拆分后原来分区不再存在。注意不能对HASH类型的分区进行拆分。
ALTER TABLE SALES SBLIT PARTITION P2 AT(TO_DATE('2003-02-01','YYYY-MM-DD')) INTO (PARTITION P21,PARTITION P22);
六、接合分区(coalesca) 
结合分区是将散列分区中的数据接合到其它分区中,当散列分区中的数据比较大时,可以增加散列分区,然后进行接合,值得注意的是,接合分区只能用于散列分区中。通过以下代码进行接合分区:
ALTER TABLE SALES COALESCA PARTITION;
七、重命名表分区 
以下代码将P21更改为P2
ALTER TABLE SALES RENAME PARTITION P21 TO P2;
八、相关查询
跨分区查询
select sum( *) from
(select count(*) cn from t_table_SS PARTITION (P200709_1)
union all
select count(*) cn from t_table_SS PARTITION (P200709_2)
);
查询表上有多少分区
SELECT * FROM useR_TAB_PARTITIONS WHERE TABLE_NAME='tableName'
查询索引信息
select object_name,object_type,tablespace_name,sum(value)
from v$segment_statistics
where statistic_name IN ('physical reads','physical write','logical reads')and object_type='INDEX'
group by object_name,object_type,tablespace_name
order by 4 desc
 
--显示数据库所有分区表的信息:
select * from DBA_PART_TABLES
 
--显示当前用户可访问的所有分区表信息:
select * from ALL_PART_TABLES
 
--显示当前用户所有分区表的信息:
select * from USER_PART_TABLES
 
--显示表分区信息 显示数据库所有分区表的详细分区信息:
select * from DBA_TAB_PARTITIONS
 
--显示当前用户可访问的所有分区表的详细分区信息:
select * from ALL_TAB_PARTITIONS
 
--显示当前用户所有分区表的详细分区信息:
select * from USER_TAB_PARTITIONS
 
--显示子分区信息 显示数据库所有组合分区表的子分区信息:
select * from DBA_TAB_SUBPARTITIONS
 
--显示当前用户可访问的所有组合分区表的子分区信息:
select * from ALL_TAB_SUBPARTITIONS
 
--显示当前用户所有组合分区表的子分区信息:
select * from USER_TAB_SUBPARTITIONS
 
--显示分区列 显示数据库所有分区表的分区列信息:
select * from DBA_PART_KEY_COLUMNS
 
--显示当前用户可访问的所有分区表的分区列信息:
select * from ALL_PART_KEY_COLUMNS
 
--显示当前用户所有分区表的分区列信息:
select * from USER_PART_KEY_COLUMNS
 
--显示子分区列 显示数据库所有分区表的子分区列信息:
select * from DBA_SUBPART_KEY_COLUMNS
 
--显示当前用户可访问的所有分区表的子分区列信息:
select * from ALL_SUBPART_KEY_COLUMNS
 
--显示当前用户所有分区表的子分区列信息:
select * from USER_SUBPART_KEY_COLUMNS
 
--怎样查询出oracle数据库中所有的的分区表
select * from user_tables a where a.partitioned='YES'
 
--删除一个表的数据是
truncate table table_name;
 
--删除分区表一个分区的数据是
alter table table_name truncate partition p5;
 

oracle_表分区


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论