Oracle 在线表重定义:将非分区表转换为分区表的最佳实践

虽然从 Oracle 12.2 开始,技术上已经可以直接通过以下方式转换非分区表为分区表:ALTER TABLE ... MODIFY

但一些较为复杂分区场景,还是建议采用在线重定义操作来更改为分区表。

什么是表重定义(Table Redefinition)?

在线表重定义(Online Table Redefinition) 允许在表正在被使用的情况下对其进行结构变更,且零停机时间

它是如何工作的?

Oracle 通过创建临时表和同步机制来实现在线重定义:

  1. 你创建一个具有目标结构的临时表(interim table)
  2. Oracle 将数据从原表复制到临时表
  3. 在此期间产生的任何变更都会被同步
  4. 最后,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;)。
回退操作:如果执行过程中发生错误,可以使用以下语句回退

sql 复制代码
BEGIN
  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 运行的生产环境。

在线重定义的优势在于:

  • ✅ 零停机时间

  • ✅ 完整的依赖对象迁移

  • ✅ 支持回滚操作

  • ✅ 数据一致性有保障

    关注我,学习更多的数据库知识!

相关推荐
treacle田2 小时前
达梦数据库-达梦数据库中link链接访问oracle 19c/11g-记录总结
数据库·oracle·达梦 link访问oracle
萌兰三太子2 小时前
RAG 向量数据库设计指南:从入门到生产
数据库·oracle
TDengine (老段)2 小时前
中原油田引入时序数据库 TDengine:写入性能提升、存储成本下降 85%
大数据·数据库·人工智能·时序数据库·tdengine·涛思数据
IT邦德2 小时前
Oracle 26ai搭建ADG Far Sync日志备库
数据库·oracle
Crazy CodeCrafter2 小时前
现在做服装,实体和电商怎么选?
大数据·数据库·人工智能·微信·开源软件·零售
一江寒逸3 小时前
零基础从入门到精通MongoDB(下篇):进阶精通篇——吃透高级查询、事务、索引优化与集群架构,成为MongoDB实战高手
数据库·mongodb·架构
sa100273 小时前
一键获取淘宝天猫商品评论:API 接口实战与多语言实现教程
数据库·oracle
huanmieyaoseng10033 小时前
Linux安装达梦数据库DM8
linux·运维·数据库
香蕉鼠片3 小时前
Mysql进阶篇
数据库·mysql·oracle