User Rating: 4 / 5

Star ActiveStar ActiveStar ActiveStar ActiveStar Inactive
 

In developpement, it can happen that you may want to shrink oracle datafiles after a huge delete (ie. drop user, drop big table, ...).

Here is a simple way to shrink datafiles using RESIZE option, but it may not be the most efficient.

RESIZE datafiles to the minimal

The following script will generate a sql file to resize every datafiles to the minimal size possible with RESIZE option.

--
-- ResizeAllDatafiles.sql
-- Run this script as sysdba
--

WHENEVER OSERROR EXIT FAILURE ;
WHENEVER SQLERROR EXIT FAILURE ;

SET TERM OFF VERIFY OFF FEED OFF ECHO OFF;
SET PAGES 0 LINES 255;

COLUMN SqlCmd FORMAT A120 WORD_WRAPPED ;
COLUMN DB_BLOCK_SIZE NEW_VALUE DbBlkSize_var ;

-- Retrieves db block size into DbBlkSize_var
SELECT value DB_BLOCK_SIZE FROM V$PARAMETER WHERE UPPER(NAME) = 'DB_BLOCK_SIZE' ;

SET TERM ON ;

PROMPT -- ;
PROMPT -- DB_BLOCK_SIZE: &DbBlkSize_var. ;
PROMPT -- SQL QUERY IN PROGRESS, IT CAN TAKE A WHILE ... ;
PROMPT -- ;

SPOOL ResizeAllDatafiles.gen.sql ;

SELECT 'SET ECHO ON ;' SqlCmd FROM dual ;

SELECT 'ALTER DATABASE DATAFILE '''
  || FILE_NAME || ''' RESIZE ' || CEIL( (NVL(HWM,1) * &&DbBlkSize_var.) /1024 /1024 ) || 'M;'
  || ' -- CurrentSize=' || CEIL( BLOCKS * &&DbBlkSize_var. /1024 /1024) || 'M'
  SqlCmd
FROM DBA_DATA_FILES DBADF
LEFT JOIN (SELECT FILE_ID, MAX(BLOCK_ID+BLOCKS-1) HWM FROM DBA_EXTENTS GROUP BY FILE_ID ) DBAFS
  ON DBADF.FILE_ID = DBAFS.FILE_ID
WHERE CEIL(BLOCKS * &&DbBlkSize_var. /1024 /1024) - CEIL((NVL(HWM,1) * &&DbBlkSize_var.) /1024 /1024 ) > 0 ;

SPOOL OFF ;

PROMPT -- ResizeAllDatafiles.gen.sql has been generated ;
PROMPT -- Run it as sysdba to resize all your datafiles ;

EXIT;

Go further with RESIZE

Actually RESIZE option can only shrink the datafile over the lastest block in use.
So it may happen that you won't be able to shrink a datafile even if less than 1% is used.

We can try to move objects at the beginning of the datafile in order to get a more efficient RESIZE.
Or even better is to move all object to a new tablespace then skrink all datafiles (move objects back if necessary)

Here is some SQL which can hel you to archieve that:

-- Get datafiles with id and percentage of used
SELECT df.file_id,
  Substr(df.tablespace_name,1,20) "Tablespace Name",
  Substr(df.file_name,1,80) "File Name",
  Round(df.maxbytes/1024/1024,0) "Max Size (M)",
  Round(df.bytes/1024/1024,0) "Actual Size (M)",
  decode(e.used_bytes,NULL,0,Round(e.used_bytes/1024/1024,0)) "Used (M)",
  decode(f.free_bytes,NULL,0,Round(f.free_bytes/1024/1024,0)) "Free (M)",
  decode(e.used_bytes,NULL,0,Round((e.used_bytes/df.bytes)*100,0)) "% Used"
FROM DBA_DATA_FILES DF
LEFT JOIN
  (SELECT file_id, sum(bytes) used_bytes
   FROM dba_extents GROUP by file_id) E
ON df.file_id = e.file_id
LEFT JOIN
  (SELECT Max(bytes) free_bytes, file_id
   FROM dba_free_space GROUP BY file_id) f
ON df.file_id = f.file_id
ORDER BY
  df.tablespace_name,
  df.file_name
;


-- Get sql to move all objects stored in datafile 16 to the same tablespace (reorganized objects)
SELECT
 CASE
  WHEN segment_type = 'TABLE' THEN 'ALTER TABLE '|| e.owner|| '.'|| e.segment_name|| ' MOVE TABLESPACE '||e.tablespace_name||' ;'
  WHEN segment_type = 'INDEX' THEN 'ALTER INDEX '|| e.owner|| '.'|| e.segment_name|| ' REBUILD TABLESPACE '||e.tablespace_name||' ;'
  WHEN segment_type = 'TABLE PARTITION' THEN 'ALTER TABLE '|| e.owner|| '.'|| e.segment_name|| ' MOVE PARTITION '|| e.partition_name|| ' TABLESPACE '||e.tablespace_name||' ;'
  WHEN segment_type = 'INDEX PARTITION' THEN 'ALTER INDEX '|| e.owner|| '.'|| e.segment_name|| ' REBUILD PARTITION '|| e.partition_name|| ' TABLESPACE '||e.tablespace_name||' ;'
  WHEN segment_type = 'LOBSEGMENT' THEN (
   SELECT 'ALTER TABLE '|| l.owner|| '.'|| l.table_name|| ' MOVE LOB('|| l.column_name|| ') STORE AS (TABLESPACE '||e.tablespace_name||') ;'
   FROM dba_lobs l
   WHERE l.owner = e.owner
   AND   l.segment_name = e.segment_name
   AND   ROWNUM = 1 )
  WHEN segment_type = 'LOBINDEX' THEN (
   SELECT 'ALTER TABLE '|| l.owner|| '.'|| l.table_name|| ' MOVE LOB('|| l.column_name|| ') STORE AS (TABLESPACE '||e.tablespace_name||') ;'
   FROM dba_lobs l
   WHERE l.owner = e.owner
   AND   l.index_name = e.segment_name
   AND   ROWNUM = 1 )
  ELSE '-- UNKNOW CASE '|| e.owner|| '.'|| e.segment_name|| ' ('|| segment_type|| ') ;'
 END "sql",
 max_block_id,
 DECODE(substr(segment_type,0,5),'INDEX',2,0) "sorthelper"
FROM (
  SELECT owner, segment_name, partition_name, segment_type, tablespace_name, MAX(block_id) max_block_id
  FROM dba_extents
  WHERE file_id = 14
  GROUP BY owner, segment_name, partition_name, segment_type, tablespace_name
  ORDER BY MAX(block_id) DESC
  ) e
ORDER BY 3, 2 DESC
;

You may want to rebuild all unusable indexes after that:

set serveroutput on size unlimited

BEGIN
 FOR rec IN (
  SELECT 'ALTER INDEX '||OWNER||'.'||INDEX_NAME||' ONLINE PARALLEL' cmd
  FROM    dba_indexes
  WHERE   status = 'UNUSABLE'
  UNION ALL
  SELECT 'ALTER INDEX '||index_owner||'.'||index_name||' REBUILD PARTITION '||partition_name||' ONLINE PARALLEL' cmd
  FROM    dba_ind_PARTITIONS
  WHERE   status = 'UNUSABLE'
  UNION ALL
  SELECT 'ALTER INDEX '||index_owner||'.'||index_name||' REBUILD SUBPARTITION '||subpartition_name||' ONLINE PARALLEL' cmd
  FROM    dba_ind_SUBPARTITIONS
  WHERE   status = 'UNUSABLE'
 ) LOOP
   dbms_output.put_line(rec.cmd);
   EXECUTE immediate rec.cmd;
 END LOOP;
END;
/

More efficient way for temporary datafile

The RESIZE option can be used for temporary datafile but it may not be very efficient with this kind of file.

Related articles
- Check how to Shrink temporary datafile


HTH,
Michel.

Enjoyed this article? Please like it or share it.

Add comment

Please connect with one of social login below (or fill up name and email)

     


Security code
Refresh