Until Oracle 10gR1, when the High Water Mark (HWM) of the
table moved forward (as new rows were inserted into the table), it could not be
moved backwards in order to decrease the table size and de-allocate space,
releasing it back to the tablespace free space. When we deleted many rows from
the table, the HWM and the table size remained and the only way to decrease its
size was by truncating the table. In 10gR1, Oracle introduced an exciting
feature called “shrink table”.
How does it work? When we perform the shrink command, Oracle
uses row movement (which must be enabled on the table) to move rows from the
last blocks of the table to the beginning of the table. After moving the rows,
a table lock takes place while Oracle moves the HWM backwards. Then, the blocks
after the HWM can be released and the table size is reduced.
The shrink table command works only for segments located in
a locally managed tablespace with automatic segment space management
Now we’ll try to understand how much space is released back
to the tablespace. In order to do this, we will perform the following:
·        
Create two tablespaces, one (tbs_uni) will use
uniform extent allocation of 1MB, the other one (tbs_sys) will use system
extent allocation.
·        
Create a table in each tablespace (with the same
structure and data).
·        
Enable row movement for both tables.
·        
Check the extents of the tables.
·        
Delete rows from both tables.
·        
Shrink both tables.
·        
Check the extents of the tables to see the
released space.
rajesh@PDB1> select tablespace_name,initial_extent,extent_management,allocation_type
  2  from dba_tablespaces
  3  where tablespace_name in
('TBS_SYSTEM','TBS_UNIFORM') ;
TABLESPACE_NAME INITIAL_EXTENT EXTENT_MAN ALLOCATIO
--------------- -------------- ---------- ---------
TBS_UNIFORM           
1048576 LOCAL      UNIFORM
TBS_SYSTEM              
65536 LOCAL      SYSTEM  
2 rows selected.
rajesh@PDB1>
rajesh@PDB1> create table tbl_uniform (id number, x
char(1024))
  2  enable row movement
  3  pctfree 0 tablespace tbs_uniform;  
Table created.  
rajesh@PDB1>
rajesh@PDB1> create table tbl_system (id number, x
char(1024))
  2  enable row movement
  3  pctfree 0 tablespace tbs_system;  
Table created.  
rajesh@PDB1> begin
  2     insert into tbl_uniform(id,x)
  3     select level , '*'
  4     from dual
  5     connect by level <= 100000;
  6
  7     insert into tbl_system(id,x)
  8     select level , '*'
  9     from dual
 10     connect by level <= 100000;
 11
 12     commit;
 13  end;
 14  /  
PL/SQL procedure successfully completed.
rajesh@PDB1> 
rajesh@PDB1> select segment_name, (blocks*8) as size_KB
,count(*)
  2  from user_extents
  3  where segment_name ='TBL_UNIFORM'
  4  group by segment_name,blocks
  5  order by 2;  
SEGMENT_NAME                     
SIZE_KB   COUNT(*)
------------------------------ ---------- ----------
TBL_UNIFORM                          1024        113  
1 row selected.  
rajesh@PDB1>
rajesh@PDB1> select segment_name, (blocks*8) as size_KB
,count(*)
  2  from user_extents
  3  where segment_name ='TBL_SYSTEM'
  4  group by segment_name,blocks
  5  order by 2;
SEGMENT_NAME                     
SIZE_KB   COUNT(*)
------------------------------ ---------- ----------
TBL_SYSTEM                             64         16
TBL_SYSTEM                           1024         63
TBL_SYSTEM                           8192          7  
3 rows selected.  
rajesh@PDB1>
For table TBL_UNIFORM, which is located in uniform tablespace, we see
113 extents each 1MB  
For table TBL_SYSTEM, which is located in system allocation we see total
86 extents of which first 16 are 64Kb, next 63 are 1MB and last 7 are 8MB.  
Now we will delete rows from the tables and shrink the tables to see the extent map.
rajesh@PDB1> delete from tbl_uniform where id <= 2500;  
2500 rows deleted.  
rajesh@PDB1> delete from tbl_system where id <= 2500;
2500 rows deleted.  
rajesh@PDB1> commit;  
Commit complete.  
rajesh@PDB1> alter table tbl_uniform shrink space;  
Table altered.  
rajesh@PDB1> alter table tbl_system shrink space;  
Table altered.  
rajesh@PDB1> select segment_name, (blocks*8) as size_KB
,count(*)
  2  from user_extents
  3  where segment_name ='TBL_UNIFORM'
  4  group by segment_name,blocks
  5  order by 2;  
SEGMENT_NAME                     
SIZE_KB   COUNT(*)
------------------------------ ---------- ----------
TBL_UNIFORM                          1024        111
1 row selected.
rajesh@PDB1>
rajesh@PDB1> select segment_name, (blocks*8) as size_KB
,count(*)
  2  from user_extents
  3  where segment_name ='TBL_SYSTEM'
  4  group by segment_name,blocks5 order by 2;
SEGMENT_NAME                     
SIZE_KB   COUNT(*)
------------------------------ ---------- ----------
TBL_SYSTEM                             64         16
TBL_SYSTEM                           1024         63
TBL_SYSTEM                           6208          1
TBL_SYSTEM                           8192          5  
4 rows selected.
rajesh@PDB1>
Now the extent maps shows,  
For table TBL_UNIFORM we see 111 extents, each 1MB (two extents less
than before)  
For table TBL_SYSTEM we see 85 extents (one extent less than before),
the first 84 extents are exactly as before, while the last one was reduced to
over 6Mb.  
The last thing we’ll do is to add 2500 rows back to the tables  
rajesh@PDB1> begin
  2     insert into tbl_uniform(id,x)
  3     select level , '*'
  4     from dual
  5     connect by level <= 2500;
  67 insert into tbl_system(id,x)
  8     select level , '*'
  9     from dual
 10     connect by level <= 2500;
 11
 12     commit;
 13  end;
 14  /  
PL/SQL procedure successfully completed.  
rajesh@PDB1> select segment_name, (blocks*8) as size_KB
,count(*)
  2  from user_extents
  3  where segment_name ='TBL_UNIFORM'
  4  group by segment_name,blocks
  5  order by 2;
SEGMENT_NAME                     
SIZE_KB   COUNT(*)
------------------------------ ---------- ----------
TBL_UNIFORM                          1024        113  
1 row selected.
rajesh@PDB1>
rajesh@PDB1> select segment_name, (blocks*8) as size_KB
,count(*)
  2  from user_extents
  3  where segment_name ='TBL_SYSTEM'
  4  group by segment_name,blocks
  5  order by 2;
SEGMENT_NAME                     
SIZE_KB   COUNT(*)
------------------------------ ---------- ----------
TBL_SYSTEM                             64         16
TBL_SYSTEM                           1024         63
TBL_SYSTEM                           6208          1
TBL_SYSTEM                           8192          6  
4 rows selected.
rajesh@PDB1>  
For table TBL_UNIFORM we see 111 extents, each 1MB (exactly as in the beginning)  
For table TBL_SYSTEM we see 86 extents again, the first 84 are exactly
the same, the new one is 8Mb but the 85th extent stayed just over
6Mb and did not grow to 8Mb again.
So where is the problem? ,the problem resides in tablespace, To find
the problem we need to add to the extent map the physical location of the
extent. The location of the extent in the file is according to the block_id
column in DBA_EXTENTS table
rajesh@PDB1> 
rajesh@PDB1> select file_id,segment_name,tablespace_name,
(blocks*8)as size_Kb,
  2        min(block_id) min_blks, max(block_id)
max_blks,
  3        count(block_id) as cnt_blks
  4  from (5 select file_id,segment_name,tablespace_name,blocks,block_id
  6  from dba_extents
  7  where segment_name ='TBL_UNIFORM'
  8  and owner = user
  9  union all10 select file_id,'Free_Space',tablespace_name,blocks,block_id
11 from dba_free_space
 12  where tablespace_name ='TBS_UNIFORM'
 13       )
 14  group by file_id,segment_name,tablespace_name,
blocks
 15  order by file_id,max_blks;
   FILE_ID
SEGMENT_NAME    TABLESPACE_NAME    SIZE_KB  
MIN_BLKS   MAX_BLKS   CNT_BLKS
---------- --------------- --------------- ---------- ----------
---------- ----------
        13
TBL_UNIFORM     TBS_UNIFORM           1024        128     
14464        113
        13 Free_Space      TBS_UNIFORM        1980416      14592     
14592          1
2 rows selected.
rajesh@PDB1>  
All extents come one after the other, while the free space is at the
end of the file.
rajesh@PDB1> select file_id,segment_name,tablespace_name,
(blocks*8)as size_Kb,
  2        min(block_id) min_blks, max(block_id)
max_blks,
  3        count(block_id) as cnt_blks
  4  from (
  5  select
file_id,segment_name,tablespace_name,blocks,block_id
  6  from dba_extents
  7  where segment_name ='TBL_SYSTEM'
  8  and owner = user
  9  union all
 10  select
file_id,'Free_Space',tablespace_name,blocks,block_id
 11  from dba_free_space
 12  where tablespace_name ='TBS_SYSTEM'
 13       )
 14  group by
file_id,segment_name,tablespace_name, blocks
 15  order by file_id,max_blks;
   FILE_ID
SEGMENT_NAME    TABLESPACE_NAME    SIZE_KB  
MIN_BLKS   MAX_BLKS   CNT_BLKS
---------- --------------- --------------- ---------- ----------
---------- ----------
        14 TBL_SYSTEM      TBS_SYSTEM              64        128        248         16
        14 TBL_SYSTEM      TBS_SYSTEM            1024        256       8192         63
        14 TBL_SYSTEM      TBS_SYSTEM            6208      13440     
13440          1
        14 Free_Space     
TBS_SYSTEM             960      14216     
14216          1
        14 TBL_SYSTEM      TBS_SYSTEM            8192       8320     
14336          6
        14 Free_Space      TBS_SYSTEM         1974272      15360     
15360          1
6 rows selected.  
rajesh@PDB1>
Note the free space after the 6MB extent. Remember the flow that got us
to this situation. We filled the table, then deleted rows, shrank the table and
inserted more rows. At first, the last extent of the table was 8MB in size, and
when we shrank the table, the extent was reduced to about 6MB, releasing about
2MB back to the tablespace. When we inserted new rows, Oracle needed to
allocate a new extent for them; however, it seems that extents cannot start at
any block in the file.  
Since this tablespace uses system allocation extent management, other
tables that need small extents may allocate this free space. Large tables,
however, will not be able to allocate this space, and when allocating their
next extent will leave an unused area.  
To sum up the problem, when we shrink a table in a system allocation
extent management tablespace, a part at the end of the table is released back
to the tablespace’s free space. Usually, this leaves a smaller than usual last
extent in the table. The next extent of the table will not always be able to
start at exactly the next block, depending on the size of the requested extent.
If this scenario happens, the space left between the extents can be allocated
only for small extents, so usually not for extents of the same table or other
large tables.
The best thing to do is to avoid this problem altogether. It depends on
your specific system and how you use the database. These are some points that
might be worth considering:
·        
If you use system allocation tablespaces with
big tables, and you delete and insert to these tables, you should consider not
using the “shrink table” feature. That way new rows can use the space the
deleted rows left without any problem or fragmentation
·        
If you only have large tables on a system
allocation tablespace and you have to shrink the tables periodically, consider
moving to uniform allocation with adequate extent size to avoid the
fragmentation problem.
