Oracle普通表改造分区表的方法总结
本文章主要讲述alter table modify改造方法,在线重定义改造方式请参考文章《Oracle表在线重定义操作总结(DBMS_REDEFINITION)》,CTAS方式请参考文章《Oracle CTAS+rename 表重建方法操作总结》
一、改造方法总结
1、DBMS_REDEFINITION在线重定义方式
2、create table nologging parallel as select /*+ parallel(a 4) */ 并发快速创建新表(分区形式),然后表名互换
3、expdp/impdp方法,非在线的改造方式
4、直接alter table xxx modify 方式将非分区表改为分区表,12.2以上版本才有的功能。
5、将表交换为一个分区表中的一个分区,然后split这个分区,然后表名互换
二、五种改造方法对比
| 方法 | 核心原理 | 优点 | 缺点 | 核心要点与注意事项 |
|---|---|---|---|---|
方法一:DBMS_REDEFINITION 在线重定义 |
创建一个新的分区表(中间表),通过内置包将源表数据在线拷贝至中间表,并在切换时通过原子操作交换表名。 | 真正的在线操作 ,业务几乎无感知; 支持回退 (ABORT_REDEF_TABLE); 可多次同步 (SYNC_INTERIM_TABLE) 控制切换时间; 支持列映射、列转换。 |
需要额外1倍表空间的临时表空间; 源表必须有主键或可用ROWID; 操作步骤较多,复杂度高; 主键列不能修改; 无法采用 NOLOGGING; 完成后必须手动收集统计信息。 |
需注意 : • 必须在同一用户下进行; • SYS / SYSTEM 用户下的表不支持; • 不支持含 LONG、BFILE、域索引的表; • 不支持含物化视图日志的表(需先删除); • 重定义期间禁止 FLASHBACK TABLE/QUERY; • 表列的默认值需提前在中间表手工创建。 |
方法二:CREATE TABLE ... AS SELECT (CTAS) |
利用 CREATE TABLE ... NOLOGGING PARALLEL AS SELECT 并发创建一个新的分区表,然后通过表名互换完成切换。 |
创建速度快,NOLOGGING + PARALLEL 产生的日志最少; 语法简单,易于理解; 不受主键、物化视图日志等限制。 |
业务需停止 (不能有DML); 必须先将表设为只读 (ALTER TABLE ... READ ONLY); 所有依赖对象(索引、约束、触发器、权限、视图、存储过程等)需全部手工重建 ,工作量大且容易遗漏; 完成后需手动收集统计信息。 |
建议 : • 在11g及以上版本可设置表为只读; • 建议将重建依赖对象的脚本提前准备好并测试通过; • 适合在维护窗口进行。 |
方法三:数据泵 (expdp/impdp) |
通过 expdp 导出源表数据,再通过 impdp 导入到一个预先创建好的分区表中。 |
速度相对较快; 语法简单; 不受主键、物化视图日志等限制。 | 业务需停止 (不能有DML); 必须先将表设为只读 ; 所有依赖对象需全部手工重建 ,工作量大; 完成后需手动收集统计信息。 | 建议 : • 适合数据量极大的场景; • 可配合 NETWORK_LINK 参数实现远程迁移; • 与方法二类似,属于非在线方式。 |
方法四:ALTER TABLE ... MODIFY (12.2+) |
Oracle 12.2 及以上版本提供的原生在线DDL,直接将普通表转换为分区表,并可选择转换时对索引的处理方式。 | 语法最简洁 ,一条DDL完成; 依赖对象(索引、约束、触发器、默认值、权限、视图等)全部自动保留 ; 无需额外表空间 ; 支持 ONLINE 模式,业务几乎无感知。 |
仅限 Oracle 12.2+ ; 不支持域索引; 不支持含物化视图日志的表; 大表会产生大量 REDO 日志(10G以上大表需谨慎评估 ); 转换后需手动收集统计信息。 |
关键技巧 : • 建议提前删除非必要索引,转换后再重建,以减少REDO并加快速度; • 参考分区(Reference Partitioning)的在线转换需 19.12+; • 内部仍需移动数据,大表需充分测试。 |
方法五:EXCHANGE + SPLIT 分区交换 |
创建一个单分区表,通过 EXCHANGE PARTITION 与普通表交换(仅改数据字典),再通过 SPLIT PARTITION 将这个大分区拆分为多个分区。 |
EXCHANGE 操作极快(仅修改数据字典,不物理移动数据); 不需要额外的数据复制。 |
SPLIT 大分区时耗时非常长(需移动数据); 所有依赖对象需全部手工重建 ,工作量大; 操作步骤多,容易出错; 转换后需手动收集统计信息。 |
现状 : • 此方法步骤繁琐且存在性能瓶颈; • 如今已基本不使用,已被方法一或方法四取代。 |
三、改造方法选择推荐
| 推荐场景 | 推荐方法 |
|---|---|
| 生产环境,业务不能停,且数据库版本在 12.2+ | 方法四 (ALTER TABLE ... MODIFY ONLINE) --- 首选,最省心 |
| 生产环境,业务不能停,但数据库版本低于 12.2 | 方法一 (DBMS_REDEFINITION) --- 唯一可靠的在线方式 |
| 有维护窗口,业务可停,表数据量巨大 (TB级) | 方法二 (CTAS) 或 方法三 (expdp/impdp) --- 速度最快,配合 NOLOGGING + 并行,日志最少 |
| 追求操作最简,版本满足要求,表数据量中等 | 方法四 --- 一条命令搞定所有依赖对象 |
| 临时测试环境,快速验证分区效果 | 方法二 (CTAS) --- 最直接方便 |
四、alter table modify改造语法
ALTER TABLE ... MODIFY 命令是从 Oracle 12c R2(12.2) 版本开始引入的特性,用于直接将一个普通的堆组织表(heap-organized table)转换为分区表
特别注意:对于大表,建议提前删除非必要索引,转换后重建,减少redo量,加快整体速度
ALTER TABLE table_name MODIFY
PARTITION BY {
RANGE (column_list) [ INTERVAL (expr) ] ( partition_definition [, partition_definition ]... ) |
LIST (column) ( partition_definition [, partition_definition ]... ) |
HASH (column) { PARTITIONS num | ( partition_definition [, partition_definition ]... ) }
}
[ SUBPARTITION BY ... ] -- 可选的子分区定义
[ ONLINE ] -- 可选,允许在线转换
[ UPDATE INDEXES ( -- 可选,用于精细控制索引转换
index_name { LOCAL | GLOBAL [ partition_definition ] } [, ...]
) ];
语法组成部分详解
PARTITION BY 子句(必需)
定义表的分区策略,与 CREATE TABLE 语法类似。
| 分区类型 | 语法示例 | 说明 |
|---|---|---|
| 范围分区 (RANGE) | PARTITION BY RANGE (id) (PARTITION p1 VALUES LESS THAN (100), ...) |
最常用,适用于按日期、ID等有序字段分区。 |
| 间隔分区 (INTERVAL) | PARTITION BY RANGE (created_date) INTERVAL (NUMTODSINTERVAL(1,'DAY')) (PARTITION p_init VALUES LESS THAN (DATE '2023-01-01')) |
RANGE分区的扩展,可自动创建新分区。 |
| 列表分区 (LIST) | PARTITION BY LIST (region) (PARTITION p1 VALUES ('EAST','WEST'), ...) |
适用于枚举值字段。 |
| 哈希分区 (HASH) | PARTITION BY HASH (id) PARTITIONS 4 |
适用于数据均匀分布的场景。 |
SUBPARTITION BY 子句(可选)
用于创建复合分区 表,对每个主分区再分子分区,常见组合有 RANGE-HASH 、RANGE-LIST 等。
ALTER TABLE sales MODIFY
PARTITION BY RANGE (sale_date) (
PARTITION p1 VALUES LESS THAN (DATE '2023-01-01'),
PARTITION p2 VALUES LESS THAN (DATE '2023-02-01')
)
SUBPARTITION BY HASH (customer_id) SUBPARTITIONS 8; -- 每个主分区再分为8个哈希子分区
ONLINE 关键字(强烈推荐)
ONLINE 关键字允许在不阻塞DML操作的情况下在线转换表。其内部机制复杂,会创建日志表记录变更并通过批量迁移完成转换。
ALTER TABLE your_table MODIFY PARTITION BY RANGE (id) (...) ONLINE;
UPDATE INDEXES 子句(关键)
此子句用于精细控制 表上现有索引的转换方式。如果不指定,Oracle会按默认规则转换,一定要慎重,尽量避免这样,默认不可控。
如果确实有索引需要转换,强烈建议明确指定索引的转换方式!!!
UPDATE INDEXES (
idx_name1 LOCAL, -- 转为本地分区索引
idx_name2 GLOBAL, -- 转为非分区全局索引
idx_name3 GLOBAL PARTITION BY RANGE (col) (PARTITION p_idx VALUES LESS THAN (MAXVALUE))
)
简单示例
1)修改为普通分区类型,类似如下
ALTER TABLE employees_convert MODIFY
PARTITION BY RANGE (employee_id) INTERVAL (100)
( PARTITION P1 VALUES LESS THAN (100),
PARTITION P2 VALUES LESS THAN (500)
) ONLINE -online 表示在线,不指定为offline,锁表
UPDATE INDEXES
( IDX1_SALARY LOCAL,
IDX2_EMP_ID GLOBAL PARTITION BY RANGE (employee_id)
( PARTITION IP1 VALUES LESS THAN (MAXVALUE))
);
2)修改为subpartition分区,类似如下
alter table TEST_TAB modify
partition by range (created_date) subpartition by hash (id)(
partition TEST_TAB_2021 values less than (to_date('01-jan-2022','dd-mon-yyyy')) (
subpartition TEST_TAB_SUB_PART_2021_1,
subpartition TEST_TAB_SUB_PART_2021_2,
subpartition TEST_TAB_SUB_PART_2021_3,
subpartition TEST_TAB_SUB_PART_2021_4
),
partition TEST_TAB_2022 values less than (to_date('01-jan-2023','dd-mon-yyyy')) (
subpartition TEST_TAB_SUB_PART_2022_1,
subpartition TEST_TAB_SUB_PART_2022_2,
subpartition TEST_TAB_SUB_PART_2022_3,
subpartition TEST_TAB_SUB_PART_2022_4
),
partition TEST_TAB_2023 values less than (to_date('01-jan-2024','dd-mon-yyyy')) (
subpartition TEST_TAB_SUB_PART_2023_1,
subpartition TEST_TAB_SUB_PART_2023_2,
subpartition TEST_TAB_SUB_PART_2023_3,
subpartition TEST_TAB_SUB_PART_2023_4
) )
online
update indexes
(
test_tab_pk global,
test_tab_created_date_idx local
);
五、测试案例
1. 实验环境准备
1.1 创建测试表并加载大量数据
-- 创建测试表 t,包含 500 万行数据
CREATE TABLE t (
id NUMBER,
col1 NUMBER,
col2 NUMBER,
col3 NUMBER,
col4 NUMBER,
padding VARCHAR2(100)
);
-- 插入 200 万行
INSERT /*+ APPEND */ INTO t
SELECT LEVEL,
MOD(LEVEL, 1000),
MOD(LEVEL, 500),
ROUND(DBMS_RANDOM.VALUE(1, 10000)),
ROUND(DBMS_RANDOM.VALUE(1, 10000)),
LPAD('X', 100, 'X')
FROM DUAL
CONNECT BY LEVEL <= 2000000;
COMMIT;
-- 收集统计信息
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T');
1.2 创建多种类型的索引
-- 1. 前缀索引(索引列包含分区键 id)
CREATE INDEX idx_prefix ON t(id);
-- 2. 非前缀普通索引(索引列不包含分区键)
CREATE INDEX idx_nonprefix ON t(col1, col2);
-- 3. 普通索引
CREATE INDEX idx_col3 ON t(col3);
-- 4. 唯一索引(包含分区键,用于观察全局唯一性约束)
CREATE UNIQUE INDEX idx_unique ON t(id, col4);
1.3 检查索引初始状态
set linesize 200 pagesize 999
col INDEX_NAME format a15
col INDEX_TYPE format a15
col UNIQUENESS format a15
SELECT INDEX_NAME, INDEX_TYPE, UNIQUENESS, PARTITIONED
FROM USER_INDEXES
WHERE TABLE_NAME = 'T';
INDEX_NAME INDEX_TYPE UNIQUENESS PARTITION
--------------- --------------- --------------- ---------
IDX_UNIQUE NORMAL UNIQUE NO
IDX_COL3 NORMAL NONUNIQUE NO
IDX_NONPREFIX NORMAL NONUNIQUE NO
IDX_PREFIX NORMAL NONUNIQUE NO
2. 场景一:默认行为(省略 UPDATE INDEXES)
2.1 执行分区转换
-- 不指定 UPDATE INDEXES,由 Oracle 自动决定索引转换方式
set timing on
ALTER TABLE t MODIFY
PARTITION BY RANGE (id) (
PARTITION p1 VALUES LESS THAN (1000000),
PARTITION p2 VALUES LESS THAN (2000000),
PARTITION p3 VALUES LESS THAN (3000000),
PARTITION p4 VALUES LESS THAN (4000000),
PARTITION p5 VALUES LESS THAN (MAXVALUE)
);
2.2 检查转换后的索引状态
-- 查看索引是否分区以及类型
SELECT INDEX_NAME, PARTITIONED, STATUS
FROM USER_INDEXES
WHERE TABLE_NAME = 'T';
INDEX_NAME PAR STATUS
--------------- --- --------
IDX_PREFIX YES N/A
IDX_NONPREFIX NO VALID
IDX_COL3 NO VALID
IDX_UNIQUE YES N/A
-- 查看分区索引的各分区状态
col partition_name format a30
SELECT INDEX_NAME, PARTITION_NAME, STATUS
FROM USER_IND_PARTITIONS
WHERE INDEX_NAME IN ('IDX_PREFIX', 'IDX_UNIQUE')
ORDER BY INDEX_NAME, PARTITION_NAME;
INDEX_NAME PARTITION_NAME STATUS
--------------- ------------------------------ --------
IDX_PREFIX P1 USABLE
IDX_PREFIX P2 USABLE
IDX_PREFIX P3 USABLE
IDX_PREFIX P4 USABLE
IDX_PREFIX P5 USABLE
IDX_UNIQUE P1 USABLE
IDX_UNIQUE P2 USABLE
IDX_UNIQUE P3 USABLE
IDX_UNIQUE P4 USABLE
IDX_UNIQUE P5 USABLE
2.3 结果
不指定(省略 UPDATE INDEXES),Oracle会按默认规则转换表上的索引!
| 索引名 | 分区状态 | 说明 |
|---|---|---|
IDX_PREFIX |
YES |
因为包含分区键 id,自动转换为本地分区索引 |
IDX_NONPREFIX |
NO |
不包含分区键,自动保留为非分区全局索引 |
IDX_COL3 |
NO |
不包含分区键,自动保留为非分区全局索引 |
IDX_UNIQUE |
YES |
唯一索引包含分区键,自动转换为本地分区索引(但若包含唯一约束,需注意是否有跨分区唯一性) |
3. 场景二:各种选项下的redo日志产生量
主要考虑online和非online模式,以及是否更新索引,这几种情况下的redo日志产生量
0、准备工作,重建初始表(与场景一完全一致)
为避免干扰,重新创建相同的表结构和数据,或者将表改回普通表并重新创建索引。
-- 回退方法(若允许)
DROP TABLE t PURGE;
-- 重新执行 1.1 和 1.2 步骤重建
-- 索引是否创建,根据场景来决定
1、有索引,非online模式,redo日志产生量
-- 不指定 UPDATE INDEXES,由 Oracle 自动决定索引转换方式
set timing on
ALTER TABLE t MODIFY
PARTITION BY RANGE (id) (
PARTITION p1 VALUES LESS THAN (1000000),
PARTITION p2 VALUES LESS THAN (2000000),
PARTITION p3 VALUES LESS THAN (3000000),
PARTITION p4 VALUES LESS THAN (4000000),
PARTITION p5 VALUES LESS THAN (MAXVALUE)
);
col name format a20
select a.name,b.value
from v$statname a,v$mystat b
where a.statistic# = b.statistic# and a.name='redo size';
非online模式下,有索引
NAME VALUE
-------------------- ----------
redo size 1908180
2、无索引,非online模式,redo日志产生量
set timing on
ALTER TABLE t MODIFY
PARTITION BY RANGE (id) (
PARTITION p1 VALUES LESS THAN (1000000),
PARTITION p2 VALUES LESS THAN (2000000),
PARTITION p3 VALUES LESS THAN (3000000),
PARTITION p4 VALUES LESS THAN (4000000),
PARTITION p5 VALUES LESS THAN (MAXVALUE)
);
col name format a20
select a.name,b.value
from v$statname a,v$mystat b
where a.statistic# = b.statistic# and a.name='redo size';
非online模式下,无索引
NAME VALUE
-------------------- ----------
redo size 683044
3、无索引,online模式,redo日志产生量
set linesize 200 pagesize 999
col INDEX_NAME format a15
col INDEX_TYPE format a15
col UNIQUENESS format a15
col name format a20
select a.name,b.value
from v$statname a,v$mystat b
where a.statistic# = b.statistic# and a.name='redo size';
ALTER TABLE t MODIFY
PARTITION BY RANGE (id) (
PARTITION p1 VALUES LESS THAN (1000000),
PARTITION p2 VALUES LESS THAN (2000000),
PARTITION p3 VALUES LESS THAN (3000000),
PARTITION p4 VALUES LESS THAN (4000000),
PARTITION p5 VALUES LESS THAN (MAXVALUE)
) online;
select a.name,b.value
from v$statname a,v$mystat b
where a.statistic# = b.statistic# and a.name='redo size';
online模式下,无索引
NAME VALUE
-------------------- ----------
redo size 1327032
4、有索引,online模式,redo日志产生量
set linesize 200 pagesize 999
col INDEX_NAME format a15
col INDEX_TYPE format a15
col UNIQUENESS format a15
col name format a20
select a.name,b.value
from v$statname a,v$mystat b
where a.statistic# = b.statistic# and a.name='redo size';
ALTER TABLE t MODIFY
PARTITION BY RANGE (id) (
PARTITION p1 VALUES LESS THAN (1000000),
PARTITION p2 VALUES LESS THAN (2000000),
PARTITION p3 VALUES LESS THAN (3000000),
PARTITION p4 VALUES LESS THAN (4000000),
PARTITION p5 VALUES LESS THAN (MAXVALUE)
) online
UPDATE INDEXES (
idx_prefix LOCAL,
idx_nonprefix GLOBAL,
IDX_COL3 LOCAL,
idx_unique GLOBAL
);
select a.name,b.value
from v$statname a,v$mystat b
where a.statistic# = b.statistic# and a.name='redo size';
online模式下,有索引
NAME VALUE
-------------------- ----------
redo size 2528856
结果对比:
| modify改造模式 | redo日志量 | 执行速度 |
|---|---|---|
| 非online模式下,有索引 | 1908180 | 第三 |
| 非online模式下,无索引 | 683044 | 第一 |
| online模式下,无索引 | 1327032 | 第二 |
| online模式下,有索引 | 2528856 | 第四 |
结论 :如果条件允许,建议提前删除非必要索引,转换后再重建,以减少REDO并加快整体速度