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
JOIN
(
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.
Instead of export/import, you should use
alter table t shrink space;
which is much less dangerous & keeps the table even accessable during the reorganization. After the shrink, you may use below script (originally from Tom Kyte) to find out to what size you can resize (making them smaller) the datafile(s):
set verify off
column file_name format a50 word_wrapped
column smallest format 999,990 heading "Smallest|Size|Poss."
column currsize format 999,990 heading "Current|Size"
column savings format 999,990 heading "Poss.|Savings"
break on report
compute sum of savings on report
column value new_val blksize
select value from v$parameter where name = 'db_block_size'
/
select file_name,
ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) smallest,
ceil( blocks*&&blksize/1024/1024) currsize,
ceil( blocks*&&blksize/1024/1024) -
ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) savings
from dba_data_files a,
( select file_id, max(block_id+blocks-1) hwm
from dba_extents
group by file_id ) b
where a.file_id = b.file_id(+)
/
column cmd format a75 word_wrapped
select 'alter database datafile '''||file_name||''' resize ' ||
ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) || 'm;' cmd
from dba_data_files a,
( select file_id, max(block_id+blocks-1) hwm
from dba_extents
group by file_id ) b
where a.file_id = b.file_id(+)
and ceil( blocks*&&blksize/1024/1024) -
ceil( (nvl(hwm,1)*&&blksize)/1024/1024 ) > 0
/
Best Answer
Tablespace size is not changed by creating, dropping or importing tables (if we forget autoextend option). If your tablespace has no data after droping tables, it is easy to resize datafiles or recreate tablespace. If not empty tablespace, after tables drop, you can try to resize datafiles ALTER DATABASE DATAFILE 'file1' RESIZE 20000m.
Note that resizing may not always be done if your tablespace still contain some tables/indexes. In this case you will need to do some other actions before resizing... (alter table move or drop+import or...)