公司内部
Oracle分区管理
主 题:Oracle分区管理
项目名称:公司内部
撰写单位:Bsoft
作 者:屈毅
版 本 号:0.1

日 期:2020-03-10

审核

功能 姓名 部门 签名 日期

文档历史

版本 修改原因 修改人 基于版本 日期
0.1 创建 屈毅 2020-03-10

参考文档

编号 文档名(链接)
1
2
3
http://www.cnblogs.com/pengineer/p/4393988.html
https://blog.csdn.net/zc309087694/article/details/52159893
https://www.2cto.com/database/201307/227464.html


**

概述

一、分区表理论知识

  1. Oracle的分区表可以包括多个分区,每个分区都是一个独立的段(SEGMENT),可以存放到不同的表空间中。查询时可以通过查询表来访问各个分区中的数据,也可以通过在查询时直接指定分区的方法来进行查询。<br />在oracle 10g中最多支持:1024k-1个分区。<br /> <br />分区提供以下优点:<br /> 1)由于将数据分散到各个分区中,减少了数据损坏的可能性;<br /> 2)可以对单独的分区进行备份和恢复;<br /> 3)可以将分区映射到不同的物理磁盘上,来分散IO;<br /> 4)提高可管理性、可用性和性能。<br /> <br />Oracle 10g提供了以下几种分区类型:<br /> 1)范围分区(range);<br /> 2)哈希分区(hash);<br /> 3)列表分区(list);<br /> 4)范围-哈希复合分区(range-hash);<br /> 5)范围-列表复合分区(range-list)。<br /> <br />这里主要介绍range分区和list分区:<br />**Range分区:**<br />以列的值的范围来做为分区的划分条件,将记录存放到列值所在的range分区中。<br /> 如按照时间划分,20101月的数据放到a分区,2月的数据放到b分区,在创建的时候,需要指定基于的列,以及分区的范围值。<br /> 在按时间分区时,如果某些记录暂无法预测范围,可以创建maxvalue分区(不是必须创建的),所有不在指定范围内的记录都会被存储到maxvalue所在分区中。<br />同时可以为每个分区指定表空间(不指定则为当前用户的默认表空间),各分区的表空间可以相同也可以不同。<br /> <br />如:<br />create table pdba (id number, time date) partition by range (time)<br />(<br />partition p201010 values less than (to_date('2010-10-1', 'yyyy-mm-dd')) tablespace tablespace01,<br />partition p201011 values less than (to_date('2010-11-1', 'yyyy-mm-dd')) tablespace tablespace01,<br />partition p201012 values less than (to_date('2010-12-1', 'yyyy-mm-dd')) tablespace tablespace02,<br />partition pother values less than (maxvalue) tablespace tablespace03<br />)<br />**List<br />  List分区也需要指定列的值,其分区值必须明确指定,该分区列只能有一个,不能像range或者hash分区那样同时指定多个列做为分区依赖列,但它的单个分区对应值可以是多个。<br />  在分区时必须确定分区列可能存在的值,一旦插入的列值不在分区范围内,则插入/更新就会失败,因此通常建议使用list分区时,要创建一个default分区存储那些不在指定范围内的记录,类似range分区中的maxvalue分区。<br /> <br />在根据某字段,如城市代码分区时,可以指定default,把非分区规则的数据,全部放到这个default分区。<br />如:<br />create table custaddr<br />(<br /> id varchar2(15) not null,<br /> areacode varchar2(4)<br /> )<br />partition by list (areacode)<br />( partition t_list025 values ('025'), <br /> partition t_list372 values ('372'),<br /> partition t_list510 values ('510'),<br />partition p_other values (default)<br />)<br /> <br />

二、普通表转分区表方法

将普通表转换成分区表有4种方法:
1. Export/import method
导入导出方法
2. Insert with a subquery method
插入法
3. Partition exchange method
交换分区法
4. DBMS_REDEFINITION
在线重定义

由于我们需要改造的表都是数据量非常大的,且为了便于操作和将业务影响减小到最低,所以建议采用导入导出法或在线重定义。下面重点介绍这两种方法:

Export/import method:

1) 导出表:
exp user/password@server tables=tablename file=exp.dmp

2) 删除原表:
drop table tablename;

3) 重建分区表:
CREATE TABLE tablename (
id NUMBER(10),
create_date DATE,
name VARCHAR2(100)
)
PARTITION BY RANGE (create_date)
(PARTITION p200501 VALUES LESS THAN (TO_DATE(‘01/01/2005’, ‘DD/MM/YYYY’)),
PARTITION p200502 VALUES LESS THAN (TO_DATE(‘01/02/2005’, ‘DD/MM/YYYY’)),
PARTITION pother VALUES LESS THAN (MAXVALUE));

4) 导入表:
imp user/password@server file=exp.dmp ignore=y

(a.如果导入的用户相同,可以将原表先RENAME TO另一个表名,将分区表建好后导入,核查无误后再将原表删除;
b.如果导入的用户不同,则可先不删除原表,直接导入另一用户下:
imp user/password@server tables=tablename file=exp.dmp fromuser=hd40 touser=hdreport ignore=y
这样可以与原表数据核查无误后,再将原表删除,将新建表赋权给hd40用户。)

5) 最后检查下数据是否一致,检查约束和索引,如没有则需重建。
检查索引状态:
select index_name,table_name,status from user_indexes;

DBMS_REDEFINITION:

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

在线重定义需要注意的几个限制条件:
必须准备两倍表大小的空间;
必须在同一用户下操作,且不能用sys用户。

在线重定义的大致操作流程如下:
(1)创建基础表A,如果存在,就不需要操作。
(2)创建临时的分区表B。
(3)开始重定义,将基表A的数据导入临时分区表B。
(4)结束重定义,此时在数据库的数据字典里,已经将2个表进行了交换。即此时基表A成了分区表,我们创建的临时分区表B 成了普通表。 此时我们可以删除我们创建的临时表B。它已经是普通表。


示例:
1. 我们要改造的基本表:
CREATE TABLE unpar_table (
id NUMBER(10),
create_date DATE,
name VARCHAR2(100)
);

ALTER TABLE unpar_table ADD (
CONSTRAINT unpar_table_pk PRIMARY KEY (id)
);

CREATE INDEX create_date_ind ON unpar_table(create_date);

2. 收集表的统计信息
EXEC DBMS_STATS.gather_table_stats(USER, ‘unpar_table’, cascade => TRUE);

3. 创建临时分区表
CREATE TABLE par_table (
id NUMBER(10),
create_date DATE,
name VARCHAR2(100)
)
PARTITION BY RANGE (create_date)
(PARTITION p200501 VALUES LESS THAN (TO_DATE(‘01/01/2005’, ‘DD/MM/YYYY’)),
PARTITION p200502 VALUES LESS THAN (TO_DATE(‘01/02/2005’, ‘DD/MM/YYYY’)),
PARTITION pother VALUES LESS THAN (MAXVALUE));

4. 进行重定义操作
4.1 检查重定义的合理性
EXEC Dbms_Redefinition.can_redef_table(USER, ‘unpar_table’);
(
PS:默认是按主键的方式进行重定义,如果没有主键,则需要设定参数为2,按照rowid重定义
EXEC Dbms_Redefinition.can_redef_table(USER, ‘unpar_table’,2);
)

4.2 如果4.1 没有问题,开始重定义,这个过程可能需要比较长的时间。
EXEC DBMS_REDEFINITION.start_redef_table( USER, ‘unpar_table’, ‘par_table’);
(
ROWID方式:
EXEC DBMS_REDEFINITION.start_redef_table( USER, ‘unpar_table’, ‘par_table’,null,2);
)
这一步操作结束后,数据就已经同步到这个临时的分区表里来了。

4.3 同步新表,这是可选的操作
Exec dbms_redefinition.sync_interim_table( USER, ‘unpar_table’, ‘par_table’);

4.4 创建主键和索引。
ALTER TABLE par_table ADD (
CONSTRAINT unpar_table_pk2 PRIMARY KEY (id)
);

CREATE INDEX create_date_ind2 ON par_table(create_date);

4.5 收集新表的统计信息
EXEC DBMS_STATS.gather_table_stats(USER, ‘par_table’, cascade => TRUE);

4.6 结束重定义
Exec dbms_redefinition.finish_redef_table( USER, ‘unpar_table’, ‘par_table’);
此时基表unpar_table 和临时分区表par_table 已经进行了交换。 此时临时分区表par_table成了普通表,我们的基表unpar_table成了分区表。
我们在重定义的时候,基表unpar_table是可以进行DML操作的。 只有在2个表进行切换的时候会有短暂的锁表。

5. 删除临时表
DROP TABLE par_table;

6. 主键和索引重命名
ALTER TABLE unpar_table RENAME CONSTRAINT unpar_table_pk2 TO unpar_table_pk;
ALTER INDEX create_date_ind2 RENAME TO create_date_ind;

三、创建存储过程自动新建分区

为了不每次都手工去增加分区,所以可以将新建分区的动作写进存储过程,通过JOB每月自动调用。
(此过程仅指定但不维护表空间,需人工定期维护表空间避免表空间不足)

标准示例:

分区的命名规则为pyyyymm,例:p201108。
分区的范围条件为yyyymm01,例:values less than(TO_DATE(‘2011-08-1’, ‘YYYY-MM-DD’))
即:p201108 存放 2011-07的数据

每次提前两个月创建新分区和新数据文件,
即:2011-05创建p201108,里面存放2011-07的数据

create or replace procedure AUTO_ADD_PARTITION(pidate date)
IS

vmsg varchar2(1000);
vdate date;
vmax date; — 目前最大分区日期
vnext date; — 要创建的分区日期
vnum varchar2(10); —日期转分区号
vcmd varchar2(500);

CURSOR C IS select DISTINCT TABLE_NAME,TABLESPACE_NAME
from dba_tab_partitions
where table_owner <> ‘SYS’
and table_owner <> ‘SYSTEM’;
—此处要注意当前用户是否有表dba_tab_partitions的select权限
begin
vdate := pidate;
vnext := add_months(last_day(trunc(vdate)) + 1, 2);

FOR R IN C LOOP
select max(to_date(substr(partition_name, 2), ‘yyyymm’))
into vmax
from dba_tab_partitions
where table_name = R.TABLE_NAME;

if vnext <= vmax then
insert into hd_log
(time, oper, modulename, type, content)
values
(sysdate, ‘添加分区’, ‘添加’||R.TABLE_NAME||’分区’, 101, ‘未到添加时间,不需添加。’);
commit;
else
vnum := to_char(vnext, ‘yyyymm’);
vcmd := ‘alter table ‘||R.TABLE_NAME||’ add partition p’||vnum|| ‘ values less than
(to_date(‘’’||VNum||’’’,’’yyyymm’’)) tablespace ‘||R.TABLESPACE_NAME||’ ‘;
execute immediate vcmd;
insert into hd_log
(time, oper, modulename, type, content)
values
(sysdate,’添加分区’,’添加分区’,101,’添加’||R.TABLE_NAME||’分区结束’);
commit;
end if;
END LOOP;
exception
when others then
begin
rollback;
vmsg := ‘添加分区出错,错误号:’ || SQLCODE || ‘错误信息:’ || SQLERRM;
insert into hd_log
(time, oper, modulename, type, content)
values
(sysdate,’添加分区’,’添加分区出错’,304,substrb(vmsg, 1, 254));
commit;
end;
end;

四、分区表的其他操作

1.
添加新的分区有2中情况:
(1)原分区里边界是maxvalue或者default。 这种情况下,我们需要把边界分区drop掉,加上新分区后,在添加上新的分区。 或者采用split,对边界分区进行拆分。
(2)没有边界分区的。 这种情况下,直接添加分区就可以了。

给SALES表添加一个P200306分区:
ALTER TABLE SALES ADD PARTITION P200306 VALUES LESS THAN(TO_DATE(‘2003-06-01’,’YYYY-MM-DD’));

2.
ALTER TABLE SALES DROP PARTITION P200306;

3.
截断某个分区是指删除某个分区中的数据,并不会删除分区,也不会删除其它分区中的数据。
ALTER TABLE SALES TRUNCATE PARTITION P200306;

4.
相邻的分区可以merge为一个分区,新分区的下边界为原来边界值较低的分区,上边界为原来边界值较高的分区:
ALTER TABLE SALES MERGE PARTITIONS P200305 ,P200306 INTO PARTITION P200306;

5.
拆分分区将一个分区拆分两个新分区,拆分后原来分区不再存在。注意不能对HASH类型的分区进行拆分。
ALTER TABLE SALES SBLIT PARTITION P200306 AT (TO_DATE(‘2003-05-15’,’YYYY-MM-DD’)) INTO (PARTITION P20030601,PARTITION P20030602);

6.移动分区
alter table tablename move partition P200306 tablespace tablespacename;

7.
ALTER TABLE SALES RENAME PARTITION P200206 TO P200306;

注意,在对分区做了除重命名之外的动作后,都需要检查索引状态是否有效,如果失效则需要重建。
检查索引状态:
select index_name,table_name,status from user_indexes;


五、分区表的相关查询

如果不指定分区查询,oracle也会自动根据查询条件进行分区筛选。

指定分区查询
Select from table partition (P201108);

显示所有分区表的信息:
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