--====================
-- 收縮表段(shrink space)
一、表的增長方式
當表被建立後,随着記錄的不斷插入,組成表的區間會被填滿,如果啟用了自動擴充,則當區間填滿後,會配置設定新的區間。假定高水
位線随着記錄的增加從最左端往右端來移動,當到底部區間的尾端時,則新的區間将會被配置設定。
二、表可收縮的原理
随着記錄的增加高水位線不斷的右移,記錄的删除不會導緻高水位線往回(左)移動
删除記錄後的空閑空間(高水位線左側)盡管可以使用,但其稀疏性導緻空間空閑
注:完整的表掃描所耗費的時間不會因為記錄的減少(删除)而減少
三、使用 alter table tbname shrink space 來收縮表段
1. 實作原理
實質上構造一個新表(在内部表現為一系列的DML操作,即将副本插入新位置,删除原來位置的記錄)
靠近末尾處(右端)資料塊中的記錄往開始處(左端)的空閑空間處移動(DML操作),不會引起DML觸發器
當所有可能的移動被完成,高水位線将會往左端移動(DDL操作)
新的高水位線右邊的空閑空間被釋放(DDL操作)
2. 實作前提條件
必須啟用行記錄轉移(enable row movement)
僅僅适用于堆表,且位于自動段空間管理的表空間(堆表包括:标準表,分區表,物化視圖容器,物化視圖日志表)
3. 不能實作收縮的表
群集表
具有LONG類型列的表
LOB段(盡管表本身可以被縮小),注,10gR2以後版本支援對LOB段的收縮
具有基于送出的物化視圖的表(因為禁用了觸發器)
具有rowid物化視圖的表(因為rowid發生了變化)
IOT映射表IOT溢出段
索引基于函數的表
未啟用行記錄轉移的堆表
4. 段收縮的優點
提高緩存使用率,提高OLTP的性能
減少磁盤I/O,提高通路速度,節省磁盤空間
段收縮是線上的,索引在段收縮期間維護,不要求額外的磁盤空間
5. 兩個選項
cascade:縮小表及其索引,并移動高水位線,釋放空間
compact:僅僅是縮小表和索引,并不移動高水位線,不釋放空間
alter table tbname shrink space 相當于帶cascade參數
四、實戰演習
1. 檢視需要收縮的表段的基本情況,此處為表big_table
SQL> col SEGMENT_NAME format a20
SQL> select OWNER,SEGMENT_NAME,BYTES/1024/1024,BLOCKS from dba_segments
2 where owner='SCOTT' and SEGMENT_NAME='BIG_TABLE';
OWNER SEGMENT_NAME BYTES/1024/1024 BLOCKS
------------------------------ -------------------- --------------- ----------
SCOTT BIG_TABLE 120 15360
SQL> select table_name,blocks,empty_blocks from dba_tables
2 where table_name='big_table' and owner='scott'; -->未更新統計資訊前BLOCKS,EMPTY_BLOCKS列為空
TABLE_NAME BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
BIG_TABLE
SQL> exec dbms_stats.gather_table_stats(ownname=>'SCOTT',tabname=>'BIG_TABLE',estimate_percent=>30);
PL/SQL procedure successfully completed.
SQL> select TABLE_NAME,BLOCKS,EMPTY_BLOCKS from dba_tables
2 where TABLE_NAME='BIG_TABLE' and owner='SCOTT';
BIG_TABLE 14590 0 -->使用gather_table_stats時不會統計EMPTY_BLOCKS塊
SQL> analyze table big_table compute statistics; -->使用analyze更新統計資訊後EMPTY_BLOCKS得到資料
Table analyzed.
BIG_TABLE 14590 770
SQL> set serveroutput on;
SQL> exec show_space('BIG_TABLE','SCOTT'); -->使用show_space過程或的BIG_TABLE上的空間配置設定資訊
Unformatted Blocks ..................... 0
FS1 Blocks (0-25) ...................... 0 -->空閑度為0-25%的塊數。FS1,FS2,FS3,FS4為空閑度所占的百分比
FS2 Blocks (25-50) ..................... 0
FS3 Blocks (50-75) ..................... 0
FS4 Blocks (75-100)..................... 0
Full Blocks ............................ 14,427
Total Blocks............................ 15,360
Total Bytes............................. 125,829,120
Total MBytes............................ 120
Unused Blocks........................... 770
Unused Bytes............................ 6,307,840
Last Used Ext FileId.................... 4
Last Used Ext BlockId................... 16,521
Last Used Block......................... 254
2. 删除記錄之後,進行收縮表段
SQL> delete from big_table where owner in ('SCOTT','SYSTEM'); -->删除記錄
8715 rows deleted.
SQL> commit;
Commit complete.
SQL> alter table big_table shrink space; -->實施shrink,提示沒有啟用ROW MOVEMENT
alter table big_table shrink space
*
ERROR at line 1:
ORA-10636: ROW MOVEMENT is not enabled
SQL> alter table big_table enable row movement; -->開啟row movement
Table altered.
SQL> alter table big_table shrink space; -->shrink成功
SQL> exec show_space('BIG_TABLE','SCOTT'); -->從下面的結果中可以看到塊數以及總大小已經變小
Unformatted Blocks ..................... 0
FS1 Blocks (0-25) ...................... 1
FS2 Blocks (25-50) ..................... 1
FS3 Blocks (50-75) ..................... 0
FS4 Blocks (75-100)..................... 0
Full Blocks ............................ 14,318
Total Blocks............................ 14,488
Total Bytes............................. 118,685,696
Total MBytes............................ 113
Unused Blocks........................... 5
Unused Bytes............................ 40,960
Last Used Ext FileId.................... 4
Last Used Ext BlockId................... 16,521
Last Used Block......................... 147
PL/SQL procedure successfully completed.
3. 驗證cascade與compact的差異
SQL> delete from big_table where rownum<8000; -->再次删除一些記錄
7999 rows deleted.
SQL> alter table big_table shrink space compact; -->使用compact方式收縮表段
Table altered.
SQL> exec show_space('BIG_TABLE','SCOTT');
Unformatted Blocks ..................... 0
FS1 Blocks (0-25) ...................... 1
FS2 Blocks (25-50) ..................... 2
FS3 Blocks (50-75) ..................... 0
FS4 Blocks (75-100)..................... 103
Full Blocks ............................ 14,214 --僅有的變化為14318-14214=104塊,即完全填滿的資料塊減少了104塊
Total Blocks............................ 14,488 --資料的總塊數及總大小并沒有減少,即未移動高水位線
Total Bytes............................. 118,685,696
Total MBytes............................ 113
Unused Blocks........................... 5
Unused Bytes............................ 40,960
Last Used Ext FileId.................... 4
Last Used Ext BlockId................... 16,521
Last Used Block......................... 147
PL/SQL procedure successfully completed.
SQL> alter table big_table shrink space cascade; -->使用cascade方式收縮,
FS4 Blocks (75-100)..................... 0
Full Blocks ............................ 14,214
Total Blocks............................ 14,384 -->總塊數及總大小均已減少
Total Bytes............................. 117,833,728
Total MBytes............................ 112
Unused Blocks........................... 4
Unused Bytes............................ 32,768
Last Used Block......................... 44
-->收縮之後索引依然有效
SQL> select OWNER,INDEX_NAME,STATUS from dba_indexes where TABLE_NAME='BIG_TABLE';
OWNER INDEX_NAME STATUS
------------------------------ ------------------------------ --------
SCOTT BIG_TABLE_PK VALID
五、文法總結:
ALTER TABLE <table_name> ENABLE ROW MOVEMENT -->前提條件
ALTER TABLE <table_name> SHRINK SPACE [ <NULL> | COMPACT | CASCADE ];
ALTER TABLE <table_name> SHRINK SPACE COMPCAT; -->縮小表和索引,不移動高水位線,不釋放空間
ALTER TABLE <table_name> SHRINK SPACE; -->收縮表,降低高水位線;
ALTER TABLE <table_name> SHRINK SPACE CASCADE; -->收縮表,降低高水位線,并且相關索引也要收縮一下
ALTER TABLE <table_name> MODIFY LOB (lob_column) (SHRINK SPACE); -->收縮LOB段
ALTER INDEX IDXNAME SHRINK SPACE; -->索引段的收縮,同表段
六、批量收縮腳本
1. 普通表(根據相應需求修改下面的語句生産相應腳本)
select'alter table '||table_name||' enable row movement;'
||chr(10)||'alter table '||table_name||' shrink space;'||chr(10)from user_tables;
select'alter index '||index_name||' shrink space;'||chr(10)from user_indexes;
2. 分區表的處理
分區表進行shrink space時發生ORA-10631錯誤.shrink space有一些限制.
在表上建有函數索引(包括全文索引)會失敗。
--根據相應需求修改下面的語句生産相應腳本
select 'alter table '||owner||'.'||table_name||' enable row movement;'
||chr(10)||'alter table '||owner||'.'||table_name||' shrink space;'||chr(10) from dba_tables
where owner=upper('&input_owner');
select 'alter index '||owner||'.'||index_name||' shrink space;'
||chr(10) from dba_indexes where uniqueness='NONUNIQUE' ;
select 'alter table '||owner||'.'||segment_name||' modify partition '||partition_name||' shrink space;'
||chr(10) from dba_segments where segment_type='TABLE PARTITION';
3. 附show_space腳本(來自Tom大師)
CREATE OR REPLACE PROCEDURE show_space
(
p_segname IN VARCHAR2,
p_owner IN VARCHAR2 DEFAULT USER,
p_type IN VARCHAR2 DEFAULT 'TABLE',
p_partition IN VARCHAR2 DEFAULT NULL
)
-- this procedure uses authid current user so it can query DBA_*
-- views using privileges from a ROLE, and so it can be installed
-- once per database, instead of once per user who wanted to use it
AUTHID CURRENT_USER AS
l_free_blks NUMBER;
l_total_blocks NUMBER;
l_total_bytes NUMBER;
l_unused_blocks NUMBER;
l_unused_bytes NUMBER;
l_LastUsedExtFileId NUMBER;
l_LastUsedExtBlockId NUMBER;
l_LAST_USED_BLOCK NUMBER;
l_segment_space_mgmt VARCHAR2(255);
l_unformatted_blocks NUMBER;
l_unformatted_bytes NUMBER;
l_fs1_blocks NUMBER;
l_fs1_bytes NUMBER;
l_fs2_blocks NUMBER;
l_fs2_bytes NUMBER;
l_fs3_blocks NUMBER;
l_fs3_bytes NUMBER;
l_fs4_blocks NUMBER;
l_fs4_bytes NUMBER;
l_full_blocks NUMBER;
l_full_bytes NUMBER;
-- inline procedure to print out numbers nicely formatted
-- with a simple label
PROCEDURE p
(
p_label IN VARCHAR2,
p_num IN NUMBER
) IS
BEGIN
dbms_output.put_line(rpad(p_label, 40, '.') ||
to_char(p_num, '999,999,999,999'));
END;
BEGIN
-- this query is executed dynamically in order to allow this procedure
-- to be created by a user who has access to DBA_SEGMENTS/TABLESPACES
-- via a role as is customary.
-- NOTE: at runtime, the invoker MUST have access to these two
-- views!
-- this query determines if the object is an ASSM object or not
EXECUTE IMMEDIATE 'select ts.segment_space_management
from dba_segments seg, dba_tablespaces ts
where seg.segment_name = :p_segname
and (:p_partition is null or
seg.partition_name = :p_partition)
and seg.owner = :p_owner
and seg.tablespace_name = ts.tablespace_name'
INTO l_segment_space_mgmt
USING p_segname, p_partition, p_partition, p_owner;
EXCEPTION
WHEN too_many_rows THEN
dbms_output.put_line('This must be a partitioned table, use p_partition => ');
RETURN;
-- if the object is in an ASSM tablespace, we must use this API
-- call to get space information, otherwise we use the FREE_BLOCKS
-- API for the user-managed segments
IF l_segment_space_mgmt = 'AUTO' THEN
dbms_space.space_usage(p_owner,
p_segname,
p_type,
l_unformatted_blocks,
l_unformatted_bytes,
l_fs1_blocks,
l_fs1_bytes,
l_fs2_blocks,
l_fs2_bytes,
l_fs3_blocks,
l_fs3_bytes,
l_fs4_blocks,
l_fs4_bytes,
l_full_blocks,
l_full_bytes,
p_partition);
p('Unformatted Blocks ', l_unformatted_blocks);
p('FS1 Blocks (0-25) ', l_fs1_blocks);
p('FS2 Blocks (25-50) ', l_fs2_blocks);
p('FS3 Blocks (50-75) ', l_fs3_blocks);
p('FS4 Blocks (75-100)', l_fs4_blocks);
p('Full Blocks ', l_full_blocks);
ELSE
dbms_space.free_blocks(segment_owner => p_owner,
segment_name => p_segname,
segment_type => p_type,
freelist_group_id => 0,
free_blks => l_free_blks);
p('Free Blocks', l_free_blks);
END IF;
-- and then the unused space API call to get the rest of the
-- information
dbms_space.unused_space(segment_owner => p_owner,
segment_name => p_segname,
segment_type => p_type,
partition_name => p_partition,
total_blocks => l_total_blocks,
total_bytes => l_total_bytes,
unused_blocks => l_unused_blocks,
unused_bytes => l_unused_bytes,
LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,
LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
LAST_USED_BLOCK => l_LAST_USED_BLOCK);
p('Total Blocks', l_total_blocks);
p('Total Bytes', l_total_bytes);
p('Total MBytes', trunc(l_total_bytes / 1024 / 1024));
p('Unused Blocks', l_unused_blocks);
p('Unused Bytes', l_unused_bytes);
p('Last Used Ext FileId', l_LastUsedExtFileId);
p('Last Used Ext BlockId', l_LastUsedExtBlockId);