dbms_redefinition是oracle 12c中唯一可靠实现普通表在线转分区表的方式,尤其适用于含虚拟列、lob、触发器等复杂场景;alter table modify partition by限制极多且易静默失败。
dbms_redefinition 是 oracle 12c 中唯一能真正“在线”完成普通表转分区表的可靠方式,尤其当你的表含虚拟列、lob、触发器、多索引或全局约束时。别被 alter table ... modify partition by 的语法迷惑——它在多数生产场景下会直接报错或静默失败。
为什么不能直接用 ALTER TABLE MODIFY PARTITION BY?
这个语法看似简洁,但限制极多,踩坑成本远高于写几行 DBMS_REDEFINITION 调用:
-
ALTER TABLE ... MODIFY PARTITION BY RANGE不支持LIST、HASH或INTERVAL分区类型 - 分区键列不能是
GENERATED ALWAYS AS虚拟列(比如你的TOTAL_AMOUNT) - 若原表某列为
NOT NULL但无DEFAULT,而新分区键列允许空值,会立即触发ORA-14097 -
UPDATE INDEXES子句对全局索引极其敏感:没显式列出依赖唯一约束的索引?ORA-14098直接中断 - 表上有物化视图日志、REF 列或审计策略?该语法直接拒绝执行
CAN_REDEF_TABLE 成功 ≠ 表真能重定义
执行 EXEC DBMS_REDEFINITION.CAN_REDEF_TABLE('SZR', 'CUSTOMER_ORDERS') 返回成功,只说明基础结构合规。你还得手动确认这几件事:
- 表必须有主键(
ORDER_ID是 OK 的),且主键列不能含SYSTIMESTAMP或SEQUENCE.NEXTVAL作为默认值(你的触发器逻辑没问题,但字段定义里不能写DEFAULT CUSTOMER_ORDERS_SEQ.NEXTVAL) - 检查是否有
ROWID引用、高级队列表依赖,或DBMS_FLASHBACK_ARCHIVE等策略启用——这些会让COPY_TABLE_DEPENDENTS失败 - 预留空间:临时中间表 + 原表数据 ≈ 2×原表大小;
TEMP表空间也要够(同步阶段大量排序用)
中间表建表时最容易错的三个细节
中间表不是复制粘贴原 DDL 就完事。稍不注意,START_REDEF_TABLE 就会抛 ORA-14197 或数据不一致:
- 字段定义必须完全一致:包括长度单位(
VARCHAR2(120)和VARCHAR2(120 CHAR)被视为不同类型) - 分区键列(如
ORDER_DATE)**不能加NOT NULL约束**,除非原表对应列也强制非空;否则START_REDEF_TABLE直接报错 - **不要自己建索引、约束、触发器**——全部交给
COPY_TABLE_DEPENDENTS同步;你手动建了,后续会冲突或丢失
示例中间表语句(注意 PARTITION BY RANGE 和分区边界写法):
CREATE TABLE SZR.CUSTOMER_ORDERS_INTERIM ( ORDER_ID NUMBER PRIMARY KEY, ORDER_DATE DATE NOT NULL, CUSTOMER_ID NUMBER NOT NULL, CUSTOMER_NAME VARCHAR2(120), PRODUCT_CODE VARCHAR2(50), QUANTITY NUMBER(8), UNIT_PRICE NUMBER(12,2), TOTAL_AMOUNT NUMBER(14,2) GENERATED ALWAYS AS (QUANTITY * UNIT_PRICE) VIRTUAL, ORDER_STATUS VARCHAR2(20) DEFAULT 'PENDING', CREATED_BY VARCHAR2(60) ) PARTITION BY RANGE (ORDER_DATE) ( PARTITION p_2023 VALUES LESS THAN (DATE '2024-01-01'), PARTITION p_2024 VALUES LESS THAN (DATE '2025-01-01'), PARTITION p_2025 VALUES LESS THAN (DATE '2026-01-01'), PARTITION p_future VALUES LESS THAN (MAXVALUE) );
重定义完成后必须手动收集统计信息
DBMS_REDEFINITION 完全不会自动更新统计信息。分区表刚上线就跑慢查询?八成是优化器还在用旧的、单表级的统计直方图。
- 执行
DBMS_STATS.GATHER_TABLE_STATS时,务必指定GRANULARITY => 'ALL',否则只扫分区头,不扫各分区内部数据分布 - 如果原表有带
HISTOGRAM的列(如ORDER_STATUS),记得加METHOD_OPT参数重新生成,不然分区裁剪可能失效 - 别漏掉索引统计:全局索引和本地索引要分别收集,
DBMS_STATS.GATHER_INDEX_STATS不能省
最简安全调用:
EXEC DBMS_STATS.GATHER_TABLE_STATS( ownname => 'SZR', tabname => 'CUSTOMER_ORDERS', granularity => 'ALL', method_opt => 'FOR ALL COLUMNS SIZE AUTO' );
真正麻烦的从来不是语法,而是那些没报错但让性能雪崩的隐性偏差——比如分区键选错、统计信息没刷、或中间表字段长度差了一个 CHAR 单位。











