虽然从 Oracle 12.2 开始,技术上已经可以直接通过以下方式转换非分区表为分区表:ALTER TABLE ... MODIFY
但一些较为复杂分区场景,还是建议采用在线重定义操作来更改为分区表。
什么是表重定义(Table Redefinition)?
在线表重定义(Online Table Redefinition) 允许在表正在被使用的情况下对其进行结构变更,且零停机时间。
它是如何工作的?
Oracle 通过创建临时表和同步机制来实现在线重定义:
- 你创建一个具有目标结构的临时表(interim table)
- Oracle 将数据从原表复制到临时表
- 在此期间产生的任何变更都会被同步
- 最后,Oracle 以原子方式将原表与临时表进行交换
测试案例
1. 创建 Sequence(用于生成 ORDER_ID)
sql
CREATE SEQUENCE SZR.CUSTOMER_ORDERS_SEQ
START WITH 1
INCREMENT BY 1
NOCACHE
NOCYCLE;
2. 创建测试用的非分区表 CUSTOMER_ORDERS(11g 兼容)
sql
CREATE TABLE SZR.CUSTOMER_ORDERS (
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)
) TABLESPACE USERS;
-- 创建 Trigger 实现自增主键
CREATE OR REPLACE TRIGGER SZR.CUSTOMER_ORDERS_TRG
BEFORE INSERT ON SZR.CUSTOMER_ORDERS
FOR EACH ROW
BEGIN
IF :NEW.ORDER_ID IS NULL THEN
SELECT SZR.CUSTOMER_ORDERS_SEQ.NEXTVAL
INTO :NEW.ORDER_ID
FROM DUAL;
END IF;
END;
/
3. 插入一些测试数据(覆盖不同年份)
sql
INSERT INTO SZR.CUSTOMER_ORDERS (ORDER_DATE, CUSTOMER_ID, CUSTOMER_NAME, PRODUCT_CODE, QUANTITY, UNIT_PRICE, ORDER_STATUS, CREATED_BY)
SELECT
ADD_MONTHS(TO_DATE('2020-03-10','YYYY-MM-DD'), LEVEL*1) AS ORDER_DATE,
5000 + MOD(LEVEL, 300) AS CUSTOMER_ID,
'Customer_' || TO_CHAR(5000 + MOD(LEVEL, 300)) AS CUSTOMER_NAME,
'PROD_' || LPAD(MOD(LEVEL, 99)+1, 3, '0') AS PRODUCT_CODE,
MOD(LEVEL, 50) + 1 AS QUANTITY,
ROUND(DBMS_RANDOM.VALUE(10, 500), 2) AS UNIT_PRICE,
CASE MOD(LEVEL, 5)
WHEN 0 THEN 'COMPLETED'
WHEN 1 THEN 'SHIPPED'
WHEN 2 THEN 'PENDING'
ELSE 'CANCELLED'
END AS ORDER_STATUS,
'User_' || MOD(LEVEL, 10) AS CREATED_BY
FROM DUAL
CONNECT BY LEVEL <= 1200;
COMMIT;
-- 查看原表记录数和数据分布
SELECT COUNT(*) AS total_rows FROM SZR.CUSTOMER_ORDERS;
SELECT MIN(ORDER_DATE), MAX(ORDER_DATE) FROM SZR.CUSTOMER_ORDERS;
SELECT TRUNC(ORDER_DATE,'MM'), COUNT(*) FROM SZR.CUSTOMER_ORDERS GROUP BY TRUNC(ORDER_DATE,'MM') ORDER BY 1;
4. 检查表是否支持重定义(使用 CONS_USE_PK)
sql
set serveroutput on;
BEGIN
DBMS_REDEFINITION.CAN_REDEF_TABLE(
uname => 'SZR',
tname => 'CUSTOMER_ORDERS',
options_flag => DBMS_REDEFINITION.CONS_USE_PK
);
DBMS_OUTPUT.PUT_LINE('表支持重定义(CONS_USE_PK):成功');
END;
/
5. 创建具有目标分区结构的临时表(Interim Table)
sql
CREATE TABLE SZR.CUSTOMER_ORDERS_INT
TABLESPACE USERS
PARTITION BY RANGE (ORDER_DATE)
INTERVAL (NUMTOYMINTERVAL(1, 'MONTH'))
(
PARTITION P_BEFORE_2022 VALUES LESS THAN (TO_DATE('2022-01-01','YYYY-MM-DD'))
)
AS
SELECT * FROM SZR.CUSTOMER_ORDERS
WHERE 1=0;
-- 查看临时表当前分区
SELECT PARTITION_NAME, HIGH_VALUE
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'CUSTOMER_ORDERS_INT';
6. 启动重定义过程(使用 CONS_USE_PK)
sql
set serveroutput on;
BEGIN
DBMS_REDEFINITION.START_REDEF_TABLE(
uname => 'SZR',
orig_table => 'CUSTOMER_ORDERS',
int_table => 'CUSTOMER_ORDERS_INT',
options_flag => DBMS_REDEFINITION.CONS_USE_PK
);
DBMS_OUTPUT.PUT_LINE('START_REDEF_TABLE 执行完成');
END;
/
大表场景 :可在 START_REDEF_TABLE 前开启并行(如
ALTER SESSION FORCE PARALLEL DML PARALLEL 4;)。
回退操作:如果执行过程中发生错误,可以使用以下语句回退
sqlBEGIN DBMS_REDEFINITION.ABORT_REDEF_TABLE( uname => 'SZR', orig_table => 'CUSTOMER_ORDERS', int_table => 'CUSTOMER_ORDERS_INT' ); END; /
7. 复制原表的依赖对象
sql
set serveroutput on;
DECLARE
l_num_errors PLS_INTEGER;
BEGIN
DBMS_REDEFINITION.COPY_TABLE_DEPENDENTS(
uname => 'SZR',
orig_table => 'CUSTOMER_ORDERS',
int_table => 'CUSTOMER_ORDERS_INT',
copy_indexes => 0, -- 不自动复制索引(主键索引已存在)
copy_triggers => TRUE,
copy_constraints => FALSE, -- 关键:关闭约束复制,避免 NOT NULL 冲突
copy_privileges => TRUE,
ignore_errors => TRUE,
num_errors => l_num_errors,
copy_statistics => FALSE
);
DBMS_OUTPUT.PUT_LINE('COPY_TABLE_DEPENDENTS 执行完成,错误数: ' || l_num_errors);
END;
/
8. 同步期间产生的新 DML 数据(推荐执行)
sql
BEGIN
DBMS_REDEFINITION.SYNC_INTERIM_TABLE(
uname => 'SZR',
orig_table => 'CUSTOMER_ORDERS',
int_table => 'CUSTOMER_ORDERS_INT'
);
DBMS_OUTPUT.PUT_LINE('SYNC_INTERIM_TABLE 执行完成');
END;
/
9. 完成重定义
sql
BEGIN
DBMS_REDEFINITION.FINISH_REDEF_TABLE(
uname => 'SZR',
orig_table => 'CUSTOMER_ORDERS',
int_table => 'CUSTOMER_ORDERS_INT'
);
DBMS_OUTPUT.PUT_LINE('FINISH_REDEF_TABLE 执行完成,表已成功转换为分区表');
END;
/
10. 验证结果
sql
SELECT TABLE_NAME, PARTITIONING_TYPE, INTERVAL
FROM USER_PART_TABLES
WHERE TABLE_NAME = 'CUSTOMER_ORDERS';
SELECT PARTITION_NAME, HIGH_VALUE, NUM_ROWS
FROM USER_TAB_PARTITIONS
WHERE TABLE_NAME = 'CUSTOMER_ORDERS'
ORDER BY PARTITION_POSITION;
SELECT COUNT(*) FROM SZR.CUSTOMER_ORDERS;
-- 删除临时表
DROP TABLE SZR.CUSTOMER_ORDERS_INT PURGE;
-- 收集统计信息
BEGIN
DBMS_STATS.GATHER_TABLE_STATS('SZR', 'CUSTOMER_ORDERS', cascade => TRUE);
END;
/
总结
通过以上步骤,我们成功使用在线表重定义技术将普通表 CUSTOMER_ORDERS 转换为了按月间隔分区表。整个过程无需停机,对业务影响最小,特别适合需要高可用 和7x24 运行的生产环境。
在线重定义的优势在于:
-
✅ 零停机时间
-
✅ 完整的依赖对象迁移
-
✅ 支持回滚操作
-
✅ 数据一致性有保障
关注我,学习更多的数据库知识!