首页 > 数据库 > Oracle > Oracle如何影响最低地回收段空间?
前面的文章里用 DBMS_SPACE 判断过"段该不该收缩":满块寥寥、fs4 空块成堆,结论是收缩能收回大量空间。但真到动手,常规手段各有痛点:ALTER TABLE MOVE 会锁表、索引全部失效要重建;SHRINK 虽然在线,但长事务下反复搬行,大表上耗时可观,而且要求开行迁移。对于不能停业务的表,第三条路是在线重定义:DBMS_REDEFINITION 包。
我的思路是:建一张结构符合预期的新表(中间表),程序自动把原表数据灌进去,并在原表上建物化视图日志跟踪重定义期间的增量修改;期间业务对原表的读写完全正常。一切就绪后执行 FINISH,把两张表的名字在数据字典里对调——原表瞬间"变成"了新表。整个过程只有 FINISH 那一刻需要短暂的排他锁,通常以秒计。
-- 检查表是否具备在线重定义资格(不合格直接报错)
DBMS_REDEFINITION .CAN_REDEF_TABLE (
uname IN VARCHAR2 ,
tname IN VARCHAR2 ,
options_flag IN PLS_INTEGER : = 1 ) ; -- 1: CONS_USE_PK 2: CONS_USE_ROWID
-- 开始重定义:灌数据 + 建跟踪日志
DBMS_REDEFINITION .START_REDEF_TABLE (
uname IN VARCHAR2 ,
orig_table IN VARCHAR2 ,
int_table IN VARCHAR2 ,
col_mapping IN VARCHAR2 : = NULL ,
options_flag IN BINARY_INTEGER : = 1 ) ;
-- 复制索引、约束、触发器、权限等从属对象
DBMS_REDEFINITION .COPY_TABLE_DEPENDENTS (
uname IN VARCHAR2 ,
orig_table IN VARCHAR2 ,
int_table IN VARCHAR2 ,
copy_indexes IN PLS_INTEGER : = 1 , -- 1: 按原索引物理参数复制
copy_triggers IN BOOLEAN : = TRUE ,
copy_constraints IN BOOLEAN : = TRUE ,
copy_privileges IN BOOLEAN : = TRUE ,
ignore_errors IN BOOLEAN : = FALSE ,
num_errors OUT PLS_INTEGER ,
copy_statistics IN BOOLEAN : = FALSE ) ;
-- 可选:把增量同步到中间表,缩短 FINISH 等待
DBMS_REDEFINITION .SYNC_INTERIM_TABLE (
uname , orig_table , int_table , ... ) ;
-- 收尾切换
DBMS_REDEFINITION .FINISH_REDEF_TABLE (
uname , orig_table , int_table , ... ) ;
-- 放弃重定义,清理过程对象
DBMS_REDEFINITION .ABORT_REDEF_TABLE (
uname , orig_table , int_table , ... ) ;
两个跟踪模式:CONS_USE_PK(默认)按主键识别行变化,要求表有主键(或全列非空的唯一键);无主键的表用 CONS_USE_ROWID,限制多一些。col_mapping 为 NULL 时按同名列全量对应,需要改造列时在这里写映射表达式 。
沿用 SMALL_TBS 那个小表空间,把一张表在线搬到 USERS 表空间,业务全程不停。
1. 授权
用户模式重定义自己 schema 的表,需要 CREATE TABLE 和 CREATE MVIEW 权限:
SQL > grant create table , create mview to test ;
SQL > grant execute on dbms_redefinition to test ;
SQL > alter user test quota unlimited on users ;
Grant succeeded.
2. 准备原表(TEST 用户)
SQL > create table t_redef (id number primary key , pad varchar2 ( 100 ))
2 tablespace small_tbs ;
Table created.
SQL > insert into t_redef select rownum , rpad ( 'x' , 100 , 'x' )
2 from dual connect by level <= 10000 ;
10000 rows created.
SQL > commit ;
Commit complete.
SQL > create index idx_t_redef on t_redef (pad ) ;
Index created.
3. 检查资格
SQL > begin
2 dbms_redefinition .can_redef_table ( 'TEST' , 'T_REDEF' ) ;
3 dbms_output .put_line ( 'ok: 候选表' ) ;
4 end ;
5 /
ok : 候选表
PL/SQL procedure successfully completed.
有主键,默认的 CONS_USE_PK 模式即可。不合格时这一步会直接报错并说明原因(比如主键缺失、表类型不支持),比 START 之后才发现问题好得多。
4. 建中间表并启动
SQL > create table t_redef_new tablespace users
2 as select * from t_redef where 1 = 0 ;
Table created.
SQL > begin
2 dbms_redefinition .start_redef_table (
3 uname => 'TEST' ,
4 orig_table => 'T_REDEF' ,
5 int_table => 'T_REDEF_NEW' ) ;
6 end ;
7 /
PL/SQL procedure successfully completed.
中间表只要结构匹配即可,用 CTAS 空拷贝最省事。START 执行两件事:把现有数据灌入中间表(这一步对大表是主要的 IO 开销,安排低峰);在原表上创建物化视图日志,此后业务对原表的 DML 都被记录。此时另一会话可以继续对 T_REDEF 正常增删改。
5. 复制从属对象
SQL > set serveroutput on
SQL > declare
2 l_errs number ;
3 begin
4 dbms_redefinition .copy_table_dependents (
5 uname => 'TEST' ,
6 orig_table => 'T_REDEF' ,
7 int_table => 'T_REDEF_NEW' ,
8 copy_indexes => 1 ,
9 copy_triggers => true ,
10 copy_constraints => true ,
11 copy_privileges => true ,
12 ignore_errors => true ,
13 num_errors => l_errs ) ;
14 dbms_output .put_line ( 'errors = ' || l_errs ) ;
15 end ;
16 /
errors = 0
PL/SQL procedure successfully completed.
ignore_errors => TRUE 让复制过程不被单个对象失败打断,错误计数与明细记在 DBA_REDEF_ERRORS 里,事后核查。主键约束、普通索引、权限都到了中间表上 。
6. 收尾切换
SQL > exec dbms_redefinition .finish_redef_table ( 'TEST' , 'T_REDEF' , 'T_REDEF_NEW' )
PL/SQL procedure successfully completed.
FINISH 把 START 之后的增量同步过来,然后在数据字典里把两张表对调。最后那一刻需要短暂排他锁,等待 DML 结束即可,通常瞬间完成。如果窗口期想更短,可在 FINISH 前先执行一次 SYNC_INTERIM_TABLE 预同步 。
7. 验证与清理
SQL > select tablespace_name from dba_tables
2 where owner = 'TEST' and table_name = 'T_REDEF' ;
TABLESPACE_NAME
------------------------------
USERS
SQL > drop table t_redef_new purge ;
Table dropped.
表已经在 USERS 里,业务侧无感。注意两点:索引随 copy_indexes => 1 按原物理参数复制,若希望索引也落到新表空间,复制后对中间表索引 ALTER INDEX ... REBUILD TABLESPACE ... 或手工重建;切换完成后中间表名的表实际是旧段,确认无误后删除回收空间 。
重定义期间在原表上自动创建物化视图日志,异常中断后用 ABORT_REDEF_TABLE 清理过程对象,不要直接删中间表了事;
克隆过来的触发器在重定义完成前不会触发,克隆的外键约束生成时是 DISABLED 状态,FINISH 后要检查 DBA_CONSTRAINTS 并手工启用,这是最容易漏的两件事;
大表的初始灌数据阶段 IO、undo 开销不亚于一次 CREATE TABLE AS SELECT,务必低峰执行,并确认 undo 与中间表所在表空间有余量;
无主键表用 CONS_USE_ROWID,但该模式不支持修改列映射等改造场景,且重定义期间原表不能做直接路径类操作;
FINISH 的短暂排他锁若遇长事务会等待,12c 起可用 dml_lock_timeout 参数设上限,超时失败再重来,避免无限等;
每张表同一时间只能有一个进行中的重定义;操作前 CAN_REDEF_TABLE、操作后查 DBA_REDEF_ERRORS,一前一后两个检查点不要省 。
对比一下三条收缩路线的取舍:MOVE 最简单但锁表、索引失效;SHRINK 在线但大表耗时长;在线重定义步骤最多,但业务影响最小,还能顺带完成压缩、分区等结构改造。表越大、业务越忙,越值得用最后这一条。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70013625/viewspace-3188623/ ,如需转载,请注明出处,否则将追究法律责任。