天天看點

收縮表段(shrink space)

--====================

-- 收縮表段(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);