Oracle表在线重定义操作总结

Oracle表在线重定义操作总结

一、DBMS_REDEFINITION(在线重定义)概述

原理:Oracle 9i引入的内置包,本质上是基于物化视图机制实现的预建快照+增量同步。通过创建一张新的分区表(影子表),在用户持续读写源表的同时,将数据同步至影子表,最后通过原子切换完成转换。

1、DBMS_REDEFINITION包:

复制代码
- ABSORT_REDEF_TABLE:清理重定义的错误和中止重定义;
- CAN_REDEF_TABLE:检查表是否可以进行重定义,存储过程执行成功代表可以进行重定义;
- COPY_TABLE_DEPENDENTS:同步索引和依赖的对象(包括索引、约束、触发器、权限等);
- FINISH_REDEF_TABLE:完成在线重定义;
- REGISTER_DEPENDENTS_OBJECTS:注册依赖的对象,如索引、约束、触发器等;
- START_REDEF_TABLE:开始在线重定义;
- SYNC_INTERIM_TABLE:增量同步数据;
- UNREGISTER_DEPENDENT_OBJECT:不注册依赖的对象,如索引、约束、触发器等;

2、相关视图

复制代码
可以通过以下视图查看在线重定义表的状态
DBA_REDEFINITION_ERRORS
DBA_REDEFINITION_OBJECTS
DBA_REDEFINITION_STATUS

3、注意事项

复制代码
优点:
真正的在线操作,业务几乎无感知
支持回退(ABORT_REDEF_TABLE)
可多次 SYNC_INTERIM_TABLE 控制最终切换时间
支持列映射、列转换等灵活操作

缺点:
需要额外 1 倍表大小的表空间
源表必须有主键或 ROWID 可用
主键列不能被修改
在线重定义无法采用nologging
必须在同一个用户下进行在线重定义。
SYS和SYSTEM用户下的表无法进行在线重定义。
不支持含 LONG、BFILE、域索引的表
不支持含物化视图日志的表(需先删除)
操作步骤较多,复杂度高
重定义期间禁止对源表执行 FLASHBACK TABLE 或 FLASHBACK QUERY
完成后必须手动收集统计信息
表列的默认值,需要提前手工创建

二、具体操作案例

本案例将一个普通表通过在线重定义方式转换为一个分区表

1、创建案例用户和授权(执行DBMS_REDEFINITION在线重定义需要的权限)

复制代码
--创建用户
create user user1 identified by user1;
--基本授权
grant connect,resource to user1;
grant unlimited tablespace to user1;
--执行DBMS_REDEFINITION在线重定义需要的权限
GRANT EXECUTE ON DBMS_REDEFINITION TO user1;
GRANT ALTER ANY TABLE TO user1;
GRANT DROP ANY TABLE TO user1;
GRANT LOCK ANY TABLE TO user1;
GRANT CREATE ANY TABLE TO user1;
GRANT SELECT ANY TABLE TO user1;
GRANT CREATE ANY TRIGGER TO user1;
GRANT CREATE ANY INDEX TO user1;

2、准备表和测试数据

复制代码
conn user1/user1
--创建表
create table BIG_TABLE (
id number(10),
created_date date default sysdate,
lookup_id number(10),
data varchar2(50)
);

--准备测试数据
--插入 1000 条模拟数据(时间分布在 2023~2026 年)
INSERT INTO BIG_TABLE (id, created_date, lookup_id, data)
SELECT
  ROWNUM+2000,
  TO_DATE('2023-01-01', 'YYYY-MM-DD') + DBMS_RANDOM.VALUE(0, 1460),
  ROUND(DBMS_RANDOM.VALUE(1, 100)),
  DBMS_RANDOM.STRING('X', ROUND(DBMS_RANDOM.VALUE(5, 50)))
FROM DUAL
CONNECT BY LEVEL <= 1000;

commit;

-- 验证数据分布
SELECT
  TO_CHAR(created_date, 'YYYY') AS year,
  COUNT(*) AS cnt
FROM BIG_TABLE
GROUP BY TO_CHAR(created_date, 'YYYY')
ORDER BY year;

3、创建表上的相关依赖对象(主要用于测试)

复制代码
--授权
grant select,insert,update,delete on big_table to scott;

-- 创建索引
create index bita_created_date_i on BIG_TABLE(created_date);
create index bita_look_fk_i on BIG_TABLE(lookup_id);

-- 约束1:主键约束
ALTER TABLE BIG_TABLE
  ADD CONSTRAINT pk_big_table PRIMARY KEY (id);
  
-- 约束2:CHECK 约束,限制 lookup_id 范围
ALTER TABLE BIG_TABLE
  ADD CONSTRAINT chk_lookup_id CHECK (lookup_id BETWEEN 1 AND 100);

-- 约束3:NOT NULL 约束
ALTER TABLE BIG_TABLE
  MODIFY data CONSTRAINT nn_data NOT NULL;
  
-- 触发器1:插入时自动生成 ID(序列 + 触发器)
CREATE SEQUENCE seq_big_table START WITH 1001 INCREMENT BY 1;

CREATE OR REPLACE TRIGGER trg_big_table_bi
BEFORE INSERT ON BIG_TABLE
FOR EACH ROW
WHEN (NEW.id IS NULL)
BEGIN
  SELECT seq_big_table.NEXTVAL INTO :NEW.id FROM DUAL;
END;
/

-- 触发器2:更新时自动记录日志到日志表
CREATE TABLE big_table_audit (
  operation    VARCHAR2(10),
  old_id       NUMBER(10),
  new_id       NUMBER(10),
  old_data     VARCHAR2(50),
  new_data     VARCHAR2(50),
  changed_by   VARCHAR2(30),
  changed_at   DATE DEFAULT SYSDATE
);

CREATE OR REPLACE TRIGGER trg_big_table_audit
AFTER UPDATE OR DELETE ON BIG_TABLE
FOR EACH ROW
BEGIN
  IF UPDATING THEN
    INSERT INTO big_table_audit (operation, old_id, new_id, old_data, new_data, changed_by)
    VALUES ('UPDATE', :OLD.id, :NEW.id, :OLD.data, :NEW.data, USER);
  ELSIF DELETING THEN
    INSERT INTO big_table_audit (operation, old_id, new_id, old_data, new_data, changed_by)
    VALUES ('DELETE', :OLD.id, NULL, :OLD.data, NULL, USER);
  END IF;
END;
/

建立 3 个视图
-- 视图1:按年份汇总统计
CREATE OR REPLACE VIEW v_big_table_yearly AS
SELECT
  TO_CHAR(created_date, 'YYYY') AS year,
  COUNT(*)                       AS total_rows,
  MIN(created_date)              AS first_date,
  MAX(created_date)              AS last_date,
  AVG(lookup_id)                 AS avg_lookup_id
FROM BIG_TABLE
GROUP BY TO_CHAR(created_date, 'YYYY');

-- 视图2:按 lookup_id 分组统计
CREATE OR REPLACE VIEW v_big_table_lookup AS
SELECT
  lookup_id,
  COUNT(*)     AS cnt,
  MIN(data)    AS sample_data,
  MAX(created_date) AS latest_date
FROM BIG_TABLE
GROUP BY lookup_id;

-- 视图3:审计日志视图(关联原表)
CREATE OR REPLACE VIEW v_big_table_audit AS
SELECT
  a.operation,
  a.old_id,
  a.new_id,
  a.old_data,
  a.new_data,
  a.changed_by,
  a.changed_at
FROM big_table_audit a
ORDER BY a.changed_at DESC;

-- 创建存储过程
CREATE OR REPLACE PROCEDURE sp_insert_big_table(
  p_count        IN NUMBER,
  p_year_start   IN NUMBER DEFAULT 2023,
  p_year_end     IN NUMBER DEFAULT 2026
)
AS
  v_date_start DATE := TO_DATE(p_year_start || '-01-01', 'YYYY-MM-DD');
  v_date_end   DATE := TO_DATE(p_year_end   || '-12-31', 'YYYY-MM-DD');
  v_days       NUMBER := v_date_end - v_date_start;
BEGIN
  FOR i IN 1 .. p_count LOOP
    INSERT INTO BIG_TABLE (id, created_date, lookup_id, data)
    VALUES (
      NULL,
      v_date_start + DBMS_RANDOM.VALUE(0, v_days),
      ROUND(DBMS_RANDOM.VALUE(1, 100)),
      DBMS_RANDOM.STRING('X', ROUND(DBMS_RANDOM.VALUE(5, 50)))
    );
  END LOOP;
  COMMIT;
  DBMS_OUTPUT.PUT_LINE('成功插入 ' || p_count);
END sp_insert_big_table;
/

-- 函数:根据 ID 查询 data 字段
CREATE OR REPLACE FUNCTION fn_get_big_table_data(
  p_id IN BIG_TABLE.id%TYPE
)
RETURN BIG_TABLE.data%TYPE
AS
  v_data BIG_TABLE.data%TYPE;
BEGIN
  SELECT data INTO v_data
  FROM BIG_TABLE
  WHERE id = p_id;
  RETURN v_data;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RETURN NULL;
END fn_get_big_table_data;
/

--创建package包
-- 包头
CREATE OR REPLACE PACKAGE pkg_big_table AS
  -- 按年份查询数据
  FUNCTION get_by_year(p_year IN NUMBER) RETURN SYS_REFCURSOR;
  -- 按 ID 删除数据
  PROCEDURE delete_by_id(p_id IN NUMBER);
  -- 统计总行数
  FUNCTION get_count RETURN NUMBER;
END pkg_big_table;
/

-- 包体
CREATE OR REPLACE PACKAGE BODY pkg_big_table AS

  FUNCTION get_by_year(p_year IN NUMBER) RETURN SYS_REFCURSOR
  AS
    v_cur SYS_REFCURSOR;
  BEGIN
    OPEN v_cur FOR
      SELECT id, created_date, lookup_id, data
      FROM BIG_TABLE
      WHERE TO_CHAR(created_date, 'YYYY') = TO_CHAR(p_year)
      ORDER BY created_date;
    RETURN v_cur;
  END get_by_year;

  PROCEDURE delete_by_id(p_id IN NUMBER)
  AS
  BEGIN
    DELETE FROM BIG_TABLE WHERE id = p_id;
    IF SQL%ROWCOUNT = 0 THEN
      RAISE_APPLICATION_ERROR(-20001, '未找到 ID = ' || p_id);
    END IF;
    COMMIT;
  END delete_by_id;

  FUNCTION get_count RETURN NUMBER
  AS
    v_cnt NUMBER;
  BEGIN
    SELECT COUNT(*) INTO v_cnt FROM BIG_TABLE;
    RETURN v_cnt;
  END get_count;

END pkg_big_table;
/


set linesize 200 pagesize 999
col constraint_name format a20
col constraint_type format a15
col triggering_event format a20
col trigger_name format a20
col view_name format a20
col object_name format a20
col object_type format a20
-- 验证数据量
SELECT COUNT(*) FROM BIG_TABLE;

-- 验证约束
SELECT constraint_name, constraint_type, status
FROM user_constraints
WHERE table_name = 'BIG_TABLE';

-- 验证触发器
SELECT trigger_name, triggering_event, status
FROM user_triggers
WHERE table_name IN ('BIG_TABLE', 'BIG_TABLE_AUDIT');

-- 验证视图
SELECT view_name FROM user_views WHERE view_name LIKE 'V_BIG_TABLE%';
SELECT object_name, object_type, status FROM user_objects WHERE object_name LIKE 'V_BIG_TABLE%';

-- 验证存储过程
SELECT object_name, object_type, status
FROM user_objects
WHERE object_name IN ('SP_INSERT_BIG_TABLE', 'FN_GET_BIG_TABLE_DATA','PKG_BIG_TABLE');

-- 验证包
set serveroutput on 
SELECT pkg_big_table.get_count FROM DUAL;

 GET_COUNT
----------
      1000

4、创建中间表(创建一个包含目标新结构的临时表,注意表列默认值,该案例为分区表)

复制代码
create table BIG_TABLE2 (
id number(10),
created_date date default sysdate,
lookup_id number(10),
data varchar2(50) )
partition by range (created_date)
(
partition BIG_TABLE_2023 values less than (to_date('01/01/2024', 'dd/mm/yyyy')),
partition BIG_TABLE_2024 values less than (to_date('01/01/2025', 'dd/mm/yyyy')),
partition BIG_TABLE_2025 values less than (maxvalue)
);

5、检查源表是否具备迁移条件

复制代码
exec dbms_redefinition.can_redef_table('user1', 'BIG_TABLE');
---检查源表是否具备迁移条件
--如果没有主键,报错如下
SQL> exec dbms_redefinition.can_redef_table('user1', 'BIG_TABLE');
BEGIN dbms_redefinition.can_redef_table('user1', 'BIG_TABLE'); END;

*
ERROR at line 1:
ORA-12089: cannot online redefine table "USER1"."BIG_TABLE" with no primary key
ORA-06512: at "SYS.DBMS_REDEFINITION", line 143
ORA-06512: at "SYS.DBMS_REDEFINITION", line 1635
ORA-06512: at line 1

6、开启数据迁移(将源表数据和中间表数据进行迁移同步)

复制代码
--检查没有问题,开启迁移数据
begin
dbms_redefinition.start_redef_table(
uname => 'user1',
orig_table => 'BIG_TABLE',
int_table => 'BIG_TABLE2');
end; 
/

7、第一全量迁移后,模拟增量

复制代码
模拟增量
INSERT INTO BIG_TABLE (id, created_date, lookup_id, data)
SELECT
  ROWNUM+2000,
  TO_DATE('2023-01-01', 'YYYY-MM-DD') + DBMS_RANDOM.VALUE(0, 1460),
  ROUND(DBMS_RANDOM.VALUE(1, 100)),
  DBMS_RANDOM.STRING('X', ROUND(DBMS_RANDOM.VALUE(5, 50)))
FROM DUAL
CONNECT BY LEVEL <= 1000;

commit;

8、增量同步

复制代码
--手动同步数据到新表,在创建索引之前,避免数据差异过大,即使finish_redef_table也会再一次同步数据
--同步增量数据(可多次执行,减少最终切换的停机时间)
begin
dbms_redefinition.sync_interim_table(
uname => 'user1',
orig_table => 'BIG_TABLE',
int_table => 'BIG_TABLE2');
end; 
/

验证是否同步增量(对比两个表数据)

9、再次模拟增量

复制代码
再次模拟增量
INSERT INTO BIG_TABLE (id, created_date, lookup_id, data)
SELECT
  ROWNUM+4000,
  TO_DATE('2023-01-01', 'YYYY-MM-DD') + DBMS_RANDOM.VALUE(0, 1460),
  ROUND(DBMS_RANDOM.VALUE(1, 100)),
  DBMS_RANDOM.STRING('X', ROUND(DBMS_RANDOM.VALUE(5, 50)))
FROM DUAL
CONNECT BY LEVEL <= 1000;

commit;

10、依赖对象迁移(自动复制原表的索引、约束、触发器和权限等到中间表)

复制代码
set serveroutput on
declare
l_errors number;
begin
dbms_redefinition.copy_table_dependents(
uname => 'user1',
orig_table => 'BIG_TABLE',
int_table => 'BIG_TABLE2',
copy_indexes => dbms_redefinition.cons_orig_params,
copy_triggers => true,
copy_constraints => true,
copy_privileges => true,
ignore_errors => false,
num_errors => l_errors,
copy_statistics => false,
copy_mvlog => false);
dbms_output.put_line('Errors=' || l_errors);
end;
/

11、收集统计信息(正式互换前)

复制代码
EXEC DBMS_STATS.gather_table_stats(USER, 'BIG_TABLE2', cascade => TRUE);

12、完成重定义

此时会在极短时间内(毫秒级)锁定原表,完成最终的数据一致性检查并切换表名,使新表生效

注:若过程中出现错误或需终止操作,必须调用 ABORT_REDEF_TABLE 清理临时日志和表*

复制代码
--完成重定义(短暂锁表,交换表名)
begin
dbms_redefinition.finish_redef_table(
uname => 'user1',
orig_table => 'BIG_TABLE',
int_table => 'BIG_TABLE2');
end; 
/



备注:(终止重定义方法如下)
BEGIN
  DBMS_REDEFINITION.ABORT_REDEF_TABLE(
    uname      => 'USER1',
    orig_table => 'BIG_TABLE',
    int_table  => 'BIG_TABLE2'
  );
END;
/

13、最终验证

复制代码
--验证表结构,是否转为分区表
SELECT partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'BIG_TABLE'
ORDER BY partition_position;

set linesize 200 pagesize 999
col constraint_name format a20
col constraint_type format a15
col triggering_event format a20
col trigger_name format a20
col view_name format a20
col object_name format a20
col object_type format a20
-- 验证数据量
SELECT COUNT(*) FROM BIG_TABLE;

-- 验证约束
SELECT constraint_name, constraint_type, status
FROM user_constraints
WHERE table_name = 'BIG_TABLE';

-- 验证触发器
SELECT trigger_name, triggering_event, status
FROM user_triggers
WHERE table_name IN ('BIG_TABLE', 'BIG_TABLE_AUDIT');

-- 验证视图
SELECT view_name FROM user_views WHERE view_name LIKE 'V_BIG_TABLE%';
SELECT object_name, object_type, status FROM user_objects WHERE object_name LIKE 'V_BIG_TABLE%';

-- 验证存储过程
SELECT object_name, object_type, status
FROM user_objects
WHERE object_name IN ('SP_INSERT_BIG_TABLE', 'FN_GET_BIG_TABLE_DATA','PKG_BIG_TABLE');

-- 测试存储过程
SET SERVEROUTPUT ON;
EXEC sp_insert_big_table(100);

-- 测试函数
SELECT fn_get_big_table_data(9000) FROM DUAL;

-- 验证包
set serveroutput on 
SELECT pkg_big_table.get_count FROM DUAL;
相关推荐
拾光Ծ27 分钟前
【MySQL】基础入门:库操库、数据表操作与数据类型详解
数据库·sql·mysql·数据类型·库操作·表操作
cspttty30 分钟前
哪些证书可以弥补学历不足
大数据·数据库·数据挖掘
Chasing__Dreams36 分钟前
向量数据库--Milvus--2--介绍
数据库·milvus
redfred44 分钟前
DBeaver 使用教程:开源免费数据库管理工具 100+ 数据库连接 SQL编辑器 ER图 AI生成SQL 全平台
数据库·sql·开源
zcmodeltech1 小时前
垃圾发电厂沙盘模型控制系统设计与实现:多设备协同联动方案
java·大数据·数据库·人工智能·stm32·嵌入式硬件·制造
有续技术1 小时前
工业网关接入 PLC,为什么要先整理点表?
运维·网络·数据库
山峰哥1 小时前
数据库工程:Explain执行计划对比调优实战‌
大数据·数据库·sql·编辑器·深度优先
夜雪一千1 小时前
MySQL Insert Intention Lock 插入意向锁是什么
数据库·mysql