1 一个常见的场景
在数据库运维中,表或索引的删除是常见的操作,删除后,表空间里会有很多剩余空间。有时,需要将这些回收到存储或者操作系统里,用作其它用途。
在Oracle 23AI之前,这个操作也可以作,但是操作起来有点难度,尤其是一个表空间包括很多数据文件时,要回收表空间的空间需要对单独的数据文件进行resize操作。Oracle 23AI提供了表空间的收缩工作,最初,这个特性只支持bigfile 表空间,后来,Oracle又将这个特定扩展到了smallfile表空间上。
2 bigfile和smallfile表空间
bigfile表空间内只能由一个数据文件,这个数据文件很大,最大可以支持到4GB个数据块。传统的标准的表空间又被称为smallfile表空间,一个表空间内最多可以支持1023(oracle 12c及以后版本)个数据文件,每个数据文件最大可以支持到2的22次方个数据块,在数据块大小为8KB时,每个数据文件支持32GB存储空间。
从Oracle 23ai开始,SYSAUX, SYSTEM, and USER 表空间缺省都是bigfile表空间。
2 表空间收缩存储过程语法及使用
Oracle 23AI中,收缩表空间要使用DBMS_SPACE包中的SHRINK_TABLESPACE存储过程,这个存储过程的语法如下
PROCEDURE SHRINK_TABLESPACE
Argument Name Type In/Out Default?
------------------------------ ----------------------- ------ --------
TS_NAME VARCHAR2 IN
SHRINK_MODE NUMBER IN DEFAULT
TARGET_SIZE NUMBER IN DEFAULT
SHRINK_RESULT CLOB OUT
存储过程的语法比较简单,只有4个参数,TS_NAME指定要收缩的表空间的名称,TARGET_SIZE设置收缩的目标,即将表空间收缩到多大,SHRINK_RESULT设置收缩操作形成的操作报告存储在哪里。SHRINK_MODE设置操作模式,表空间收缩共有3种模式,如下:
- TS_MODE_ANALYZE 只做分析,不执行。
- TS_MODE_SHRINK 缺省,只执行在线移动数据对象操作
- TS_MODE_SHRINK_FORCE 默认执行在线移动数据对象操作, 但是如果在线移动失败,将尝试执行对象的离线移动操作。
这个存储过程的四个参数中,有默认值的不是强制参数,可以不提供,使用默认值,没有默认值的则必须提供。
3 表空间收缩分析
在执行表空间收缩之前,应先执行一下收缩分析,看一下收缩会达到的效果,如下
SQL> set long 10000
SQL> variable result clob --定义变量用于输入分析操作形成的分析报告,这个时强制的变量,如果不定义这个变量,存储过程执行会报错
SQL> col result format a200
SQL> execute DBMS_SPACE.SHRINK_TABLESPACE('RECLAIMTS', SHRINK_MODE => DBMS_SPACE.TS_MODE_ANALYZE, SHRINK_RESULT => :result);
PL/SQL procedure successfully completed.
SQL> print result
RESULT
----------------------------------------------------------------------------------
Movable Objects:
1. { RECLAIM_USER.T2 | type: TABLE | blocks: 20480 | tablespace_name: RECLAIMTS }
2. { RECLAIM_USER.T2_PK | type: INDEX | blocks: 32 | tablespace_name: RECLAIMTS }
Total Movable Objects: 2
Total Movable Size(GB): .16
Original Datafile Size(GB): 1.01
Suggested Target Size(GB): .38
Process Time: +00 00:00:01.669704
报告显示了可以移动的对象,移动的大小,数据文件初始的大小,建议移动的目标大小。
4 表空间收缩实际操作
1)操作前的准备工作
a 创建表空间
QL> create bigfile tablespace reclaimts datafile size 10M autoextend on next 5M; --创建表空间
Tablespace created.
SQL> create user reclaim_user identified by reclaim default tablespace reclaimts quota unlimited on reclaimts;--创建用户使用前面创建的表空间
User created.
SQL> grant connect, resource, dba to reclaim_user; --给用户授权
Grant succeeded.
b 创建表,载入数据
SQL> connect reclaim_user/reclaim@<servicename>
SQL> create table t1 (
id number,
col1 varchar2(4000),
col2 varchar2(4000),
constraint t1_pk primary key (id));
SQL> create table t2 (
id number,
col1 varchar2(4000),
col2 varchar2(4000),
constraint t2_pk primary key (id));
SQL> insert /*+append*/ into t1 select rownum, rpad('x', 4000, 'x'), rpad('x', 4000, 'x')
from dual connect by level <= 10000;
10000 rows created.
SQL> commit;
SQL> insert /*+append*/ into t2 select rownum, rpad('x', 4000, 'x'), rpad('x', 4000, 'x')
from dual connect by level <= 10000;
10000 rows created.
SQL> commit;
c 收集兑现统计信息,检查对象大小
SQL> exec dbms_stats.gather_schema_stats('RECLAIM_USER');--收集RECLAIM_USER用户的对象统计信息
PL/SQL procedure successfully completed.
SQL> col tablespace_name format a25
SQl> select tablespace_name, blocks, bytes/1024/1024 as size_mb
from dba_data_files
where tablespace_name = 'RECLAIMTS';
TABLESPACE_NAME BLOCKS SIZE_MB
------------------------- ---------- ----------
RECLAIMTS 132480 1035
SQL> col segment_name format a10
SQL> select segment_name, segment_type, bytes/1024 size_kb
from user_segments;
SEGMENT_NA SEGMENT_TY SIZE_KB
---------- ---------- ----------
T1 TABLE 163840
T2 TABLE 163840
T1_PK INDEX 256
T2_PK INDEX 256
2)模拟删除操作,截断其中一个表
a 截断表后收集统计信息
SQL> truncate table t1;
Table truncated.
SQL> exec dbms_stats.gather_schema_stats('RECLAIM_USER');
PL/SQL procedure successfully completed.
b 检查表空间信息
SQL> col segment_name format a10
SQL> select segment_name, segment_type, bytes/1024 size_kb
from user_segments; --表空间里的分段信息
SEGMENT_NA SEGMENT_TY SIZE_KB
---------- ---------- ----------
T1 TABLE 64
T1_PK INDEX 64
T2 TABLE 163840
T2_PK INDEX 256
SQL> select ts.tablespace_name, sum(nvl(fs.bytes,0))/1024/1024 as MB_FREE, --表空间信息
count(*) as FRAGMENTS, max(nvl(fs.bytes,0))/1024/1024 as BIGGEST
from user_free_space fs, user_tablespaces ts
where fs.tablespace_name(+) = ts.tablespace_name and ts.tablespace_name='RECLAIMTS'
group by ts.tablespace_name;
TABLESPACE_NAME MB_FREE FRAGMENTS BIGGEST
------------------------- ---------- ---------- ----------
RECLAIMTS 806.5625 5 646
SQL> col segment_type format a10
SQL> set pagesize 100
SQl> select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,--表空间扩展使用信息
segment_name, segment_type
from dba_extents where tablespace_name = 'RECLAIMTS'
union all
select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,
'free' segment_name, null segment_type
from dba_free_space
where tablespace_name = 'RECLAIMTS'
order by 1 desc;
BEGIN_BLOCK END_BLOCK BLOCKS MB SEGMENT_NA SEGMENT_TY
----------- ---------- ---------- ---------- ---------- ----------
49792 132479 82688 646 free --第一个空闲空间从块49792开始
48768 49791 1024 8 T2 TABLE
47744 48767 1024 8 T2 TABLE
46720 47743 1024 8 T2 TABLE
45696 46719 1024 8 T2 TABLE
44672 45695 1024 8 T2 TABLE
43648 44671 1024 8 T2 TABLE
42624 43647 1024 8 T2 TABLE
41600 42623 1024 8 T2 TABLE
40576 41599 1024 8 T2 TABLE
39552 40575 1024 8 T2 TABLE
...
29312 29319 8 .0625 T2 TABLE
9216 29311 20096 157 free --第二个可以看到的空闲空间从9216开始
1024 1279 256 2 free
1016 1023 8 .0625 T2 TABLE
1008 1015 8 .0625 T2 TABLE
...
3) 实际收缩前,先执行一下分析操作
SQL> set linesize window
SQL> variable result clob
SQL> col result format a200
SQL> execute DBMS_SPACE.SHRINK_TABLESPACE('RECLAIMTS', SHRINK_MODE => DBMS_SPACE.TS_MODE_ANALYZE, SHRINK_RESULT => :result);
PL/SQL procedure successfully completed.
SQL> print result
RESULT
----------------------------------------------------------------------------------
Movable Objects:
1. { RECLAIM_USER.T2 | type: TABLE | blocks: 20480 | tablespace_name: RECLAIMTS }
2. { RECLAIM_USER.T2_PK | type: INDEX | blocks: 32 | tablespace_name: RECLAIMTS }
Total Movable Objects: 2
Total Movable Size(GB): .16
Original Datafile Size(GB): 1.01
Suggested Target Size(GB): .38
Process Time: +00 00:00:01.669704
可移动的对象是表T2极其索引,这时表空间里仅剩的两个对象了。
4)执行收缩操作
更改一下操作模式参数即可
SQL> set serveroutput on
SQL> execute dbms_space.shrink_tablespace('RECLAIMTS');
-------------------SHRINK RESULT-------------------
Total Moved Objects: 2
Total Moved Size(GB): .16
Original Datafile Size(GB): 1.01
New Datafile Size(GB): .39
Process Time: +00 00:00:10.611905
PL/SQL procedure successfully completed.
收缩操作执行完成,新的数据文件大小为0.39GB。检查一下表空间信息
SQL> select ts.tablespace_name, sum(nvl(fs.bytes,0))/1024/1024 as MB_FREE,
count(*) as FRAGMENTS, max(nvl(fs.bytes,0))/1024/1024 as BIGGEST
from user_free_space fs, user_tablespaces ts
where fs.tablespace_name(+) = ts.tablespace_name and ts.tablespace_name='RECLAIMTS'
group by ts.tablespace_name;
TABLESPACE_NAME MB_FREE FRAGMENTS BIGGEST
------------------------- ---------- ---------- ----------
RECLAIMTS 168.5625 5 167
表空间的空闲空间和最大空闲空间段大小都减小了。再看一下表空间的扩展分布
SQL> col segment_type format a10
SQL> set pagesize 100
SQl> select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,
segment_name, segment_type
from dba_extents
where tablespace_name = 'RECLAIMTS'
union all
select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,
'free' segment_name, null segment_type
from dba_free_space
where tablespace_name = 'RECLAIMTS'
order by 1 desc;
BEGIN_BLOCK END_BLOCK BLOCKS MB SEGMENT_NA SEGMENT_TY
----------- ---------- ---------- ---------- ---------- ----------
49792 50815 1024 8 T2 TABLE
28416 49791 21376 167 free --可以用来移动数据库对象
27392 28415 1024 8 T2 TABLE
26368 27391 1024 8 T2 TABLE
25344 26367 1024 8 T2 TABLE
24320 25343 1024 8 T2 TABLE
23296 24319 1024 8 T2 TABLE
22272 23295 1024 8 T2 TABLE
21248 22271 1024 8 T2 TABLE
20224 21247 1024 8 T2 TABLE
19200 20223 1024 8 T2 TABLE
...
1056 1151 96 .75 free --空闲空间
1048 1055 8 .0625 T2_PK INDEX
1040 1047 8 .0625 T2_PK INDEX
...
5) 再次分析一下,还有进一步收缩的可能吗?
SQL> variable result clob
SQL> col result format a200
SQL> execute DBMS_SPACE.SHRINK_TABLESPACE('RECLAIMTS', SHRINK_MODE => DBMS_SPACE.TS_MODE_ANALYZE, SHRINK_RESULT => :result);
SQL> print result
RESULT
---------------------------------------------------------------------------------
Movable Objects:
1. { RECLAIM_USER.T2 | type: TABLE | blocks: 20480 | tablespace_name: RECLAIMTS }
2. { RECLAIM_USER.T2_PK | type: INDEX | blocks: 32 | tablespace_name: RECLAIMTS }
Total Movable Objects: 2
Total Movable Size(GB): .16
Original Datafile Size(GB): .39
Suggested Target Size(GB): .38
Process Time: +00 00:00:01.674684
报告显示,还有收缩的可能
6) 再次运行收缩操作
SQL> set serveroutput on;
SQL> execute dbms_space.shrink_tablespace('RECLAIMTS');
-------------------SHRINK RESULT-------------------
Total Moved Objects: 2
Total Moved Size(GB): .16
Original Datafile Size(GB): .39
New Datafile Size(GB): .37
Process Time: +00 00:00:11.726847
PL/SQL procedure successfully completed.
执行完毕,数据文件大小缩小到0.37G,看一下表空间信息
SQL> select ts.tablespace_name, sum(nvl(fs.bytes,0))/1024/1024 as MB_FREE,
count(*) as FRAGMENTS, max(nvl(fs.bytes,0))/1024/1024 as BIGGEST
from user_free_space fs, user_tablespaces ts
where fs.tablespace_name(+) = ts.tablespace_name and ts.tablespace_name='RECLAIMTS'
group by ts.tablespace_name;
TABLESPACE_NAME MB_FREE FRAGMENTS BIGGEST
-------------------- ---------- ---------- ----------
RECLAIMTS 152.5625 5 150
空闲空间减小为152.5625MB,再看一下表空间的扩展分布
SQL> select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,
segment_name, segment_type
from dba_extents where tablespace_name = 'RECLAIMTS'
union all
select block_id begin_block, block_id + blocks - 1 end_block, blocks, bytes/1024/1024 MB,
'free' segment_name, null segment_type
from dba_free_space where tablespace_name = 'RECLAIMTS' order by 1 desc;
BEGIN_BLOCK END_BLOCK BLOCKS MB SEGMENT_NA SEGMENT_TYPE
----------- ---------- ---------- ---------- ---------- --------------------
47744 48767 1024 8 T2 TABLE
46720 47743 1024 8 T2 TABLE
45696 46719 1024 8 T2 TABLE
44672 45695 1024 8 T2 TABLE
43648 44671 1024 8 T2 TABLE
42624 43647 1024 8 T2 TABLE
41600 42623 1024 8 T2 TABLE
...
28416 28543 128 1 T2 TABLE
9216 28415 19200 150 free <= first free space...
同前面的扩展分布信息对比一下,发现数据块28416至49791的空闲空间内被表移动来的表T2的数据占据了。
5 总结及注意事项
从上面的操作可以看出,Oracle 23AI的表空间收缩操作使用起来还是比较简单的,要注意的一是再执行实际收缩操作之前先执行分析操作,预估一下操作的效果后再决定是否执行。二是表空间的收缩操作涉及到数据对象的移动,如果数据对象较大,移动的数据过多会造成大量的IO操作,应该选择系统负载有空余的时间进行操作。




