On Oracle version 11g:

After Googling, I can't find a simple way to reclaim free space after deleting a table.

I've found a lot of explanations, telling how the datafile becomes fragmented, the big stack of boring queries you have to run in order to move the "empty space" at end of the datafile (table by table... even when you have 200 tables !?).

Then you have to reduce the datafile size by "guessing" by how much you can reduce it, or you must know exactly what is your "block size"... And finally you should not forget to "rebuild the indexes".

See for example : http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:54178027703899

and http://www.oracle-base.com/articles/misc/ReclaimingUnusedSpace.php

Is there a simple PL/SQL procedure which, given a tablespace name or datafile name, would to that job? Or any similar Oracle tool?

  • Interesting info: check if your tablespaces are "locally managed" or "directory managed". The former seems to have a better handling of "defragmenting" datafiles. See: orafaq.com/node/3 Commented Jan 2, 2013 at 13:28

5 Answers 5


The short answer is No. Unfortunately the way to do this in Oracle does require the "big stack of boring queries". The articles you linked to are some of the best information available on the subject. The datafile does indeed become fragmented, so that even if free space exists below the highest segment, Oracle will not automatically consolidate it when a RESIZE is done.

To "defragment" the tablespace you need to move these segments to the start of the datafile rather than at the end. For tables this is an offline process meaning the table will be unavailable while the move is taking place. Indexes can be moved either offline or with Enterprise Edition they can be moved online. Since you have an outage window I recommend you follow these steps.

A. Shrink datafiles with free space beyond the high water mark. This can be done as follows (the query is similar to Frosty Z's procedure):

SELECT ceil( blocks*(a.BlockSize)/1024/1024) "Current Size",
   ceil( (nvl(hwm,1)*(a.BlockSize))/1024/1024 ) "Smallest Poss.",
   ceil( blocks*(a.BlockSize)/1024/1024) -
   ceil( (nvl(hwm,1)*(a.BlockSize))/1024/1024 ) "Savings",
   'alter database datafile '''|| file_name || ''' resize ' || 
      ceil((nvl(hwm,1)*(a.BlockSize))/1024/1024/100)*100  || 'm;' "Command"
FROM (SELECT a.*, p.value BlockSize FROM dba_data_files a 
JOIN v$parameter p ON p.Name='db_block_size') a
LEFT JOIN (SELECT file_id, max(block_id+blocks-1) hwm FROM dba_extents GROUP BY file_id ) b
ON a.file_id = b.file_id
WHERE ceil( blocks*(a.BlockSize)/1024/1024) - ceil( (nvl(hwm,1)*(a.BlockSize))/1024/1024 ) 
   > 100 /* Minimum MB it must shrink by to be considered. */
ORDER BY "Savings" Desc;

B. After shrinking things above the high water mark, find out what tablespaces would still benefit from having segments moved.

SELECT DISTINCT tablespace_name FROM
    SELECT tablespace_name, block_id + blocks LastBlock,
       lead(block_id) OVER (PARTITION BY File_ID 
          ORDER BY tablespace_name, file_id, block_id) NextBlock
       FROM dba_free_space 
) WHERE LastBlock <> NextBlock AND NextBlock IS NOT NULL;

C. For each of these tablespaces determine which segments need to be moved. (Replace USERS with the name of your tablespace or join it with the previous query)

SELECT distinct de.segment_name
FROM dba_extents de
   SELECT tablespace_name, file_id, MIN(block_id) LowestFreeBlock
   FROM dba_free_space
   WHERE tablespace_name = 'USERS'
  GROUP BY tablespace_name, file_id
) dfs ON dfs.tablespace_name = de.tablespace_name AND dfs.file_id = de.file_id
WHERE de.tablespace_name = 'USERS'
AND de.block_id > dfs.LowestFreeBlock;

D. Move each table and rebuild the indexes and statistics.

E. Repeat step A.

I just built most of these queries, so you will want to thoroughly test them before use. I suppose you could create a procedure that would use EXECUTE IMMEDIATE to create the actual statements to run dynamically, but because queries will receive ORA-08103: Object no longer exists while the move is in progress, I think it is best to control that process manually even if it does mean a bit more time/effort.


Partial solution inspired from this page:

It does not reorganize free space but automatically detects available free space at end of datafiles and prints out the proper 'RESIZE' commands.



            WHERE DBADF.FILE_ID = DBAFS.FILE_ID(+) AND CEIL(BLOCKS*BLKSIZE/1024/1024)- CEIL((NVL(HWM,1)* BLKSIZE)/1024/1024 ) > 0
    ) LOOP

Before you try to shrink datafiles at all, ask yourself: Are you going to create new segments again inside the associated tablespace somewhen in the not so far future? If yes, there is no point in shrinking. The space will just get reused for your new segments and you save yourself and the system much effort by leaving it as it is.


After surfing google for days i have found the most simple and clear example to reclaim the free space in the tablespace after delete. I hope this helps

Link: http://www.dbforums.com/oracle/976248-how-reduce-tablespaces-used-space-after-delete-records-2.html



Let's create a table with 9999 rows in it, each sized around 1k:

SQL> create table t (x char(1000) default 'x' primary key);
Table created.
SQL> insert /*+ append nologging */ into t(x) select rownum from all_objects where rownum < 10000;
9999 rows created.
SQL> commit;
Commit complete.

The table has 29 extents allocated to it, for a total of 14.6M:

SQL> select count(*), sum(bytes) from user_extents where segment_name='T';
---------- ----------
29 14680064

Let's delete ALL of the rows:

SQL> delete from t;
9999 rows deleted.
SQL> commit;
Commit complete.

Now- "surprise" - the table still uses the same extents:

SQL> select count(*), sum(bytes) from user_extents where segment_name='T';
---------- ----------
29 14680064

Why ? Because even if you delete all the rows of the table, the High Water Mark is not decreased - it is never decreased, to allow for maximum concurrency (Oracle is dead serious about maximizing concurrency i.e. performance and scalability; it's the main reason behind its success in Enterprise applications).

Deallocating the unused space (=space above the HWM) doesn't help much (since there is not a lot of unused space above the HWM):

SQL> alter table t deallocate unused;
Table altered.
SQL> select count(*), sum(bytes) from user_extents where segment_name='T';
---------- ----------
29 13959168

Now, let's MOVE the table, which in essence means to clone the table (including triggers, constraints and so on), transfer the rows, drop the "old" table and rename the new - all made by the kernel, so super-safe even in case of machine/server failure:

SQL> alter table t move;
Table altered.

Now, we have now only the initial extent allocated:

SQL> select count(*), sum(bytes) from user_extents where segment_name='T';
---------- ----------
1 65536

Caveat: it normally happens that many/all of the indexes on the table are UNUSABLE after the move (not in this case but i'm running, the latest release, which has probably optimized the process in case of totally empty tables):

SQL> col table_name form a30
SQL> col index_name form a30
SQL> set lines 123 
SQL> select table_name, index_name, status from user_indexes where table_name='T';

------------------------------ ------------------------------ ------------------------

If STATUS were not VALID, you could simply rebuild manually the index(es):

SQL> alter index SYS_C002573 rebuild;
Index altered.

Or you could automate the whole process:

set serveroutput on size 100000
for n in (select index_name from user_indexes where status <> 'VALID') loop
dbms_output.put_line ('rebuilding ' || n.index_name);
execute immediate 'alter index ' || n.index_name || ' rebuild';
end loop;

As an example, let's manually set the index to UNUSABLE:

SQL> alter index SYS_C002573 unusable;
Index altered.

SQL> set serveroutput on size 100000
SQL> begin
2 for n in (select index_name from user_indexes where status <> 'VALID') loop
3 dbms_output.put_line ('rebuilding ' || n.index_name);
4 execute immediate 'alter index ' || n.index_name || ' rebuild';
5 end loop;
6 end;
7 /
rebuilding SYS_C002573

PL/SQL procedure successfully completed.

HTH Alberto


As said earlier you will have to move all the 200+ tables in that tablespace to free up some space in your datafile and then resize to reclaim the space. But instead of running all those queries, 12c Enterprise manager does this task. You will have to navigate to Database Home > Storage > Tablespace. Select the tablespace you want to work on and click Reorganize. It will give an option to view the SQL statements that are about to be executed. You can either take a copy of them and run it yourself or schedule a job in EM.

It actually creates another tablespace, moves all the objects to the new tablespace, rebuilds the indexes and drops the objects from the old tablespace.

There are a couple of drawbacks I can think of. This should be done during off-peak hours else it will error out saying the resource is busy. The datafile (not tablespace) will have "reorg" added to its name, towards the end.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.