Pro*C program to defragment free space in a given tablespace

From: David Ruthven <druthven_at_lucifer>
Date: Wed, 5 May 1993 14:44:57 GMT
Message-ID: <1993May5.144457.1793_at_oracle.us.oracle.com>


Included at the end of this article is a Pro*C program which will coalesce free extents in a given tablespace. This operation is often required when database objects with differing storage clauses have been created and dropped leaving large numbers of fragmented free extents. Oracle works best if the free space in a tablespace is made up of single large extents rather than numerous small ones.

The program relies on the fact that when an object, in this case a temporary table, is dropped its extents are put on the free list as single coalesced extents even if when the extents were originally allocated they were made up of multiple contiguous free blocks.

The program works by creating temporary tables with initial extents calculated to occupy any adjacent free extents. Since Oracle will break up a large extent in preference to using adjacent extents, the program creates tables to occupy these extents to force Oracle to use adjacent extents. When there are no more adjacent free extents in the tablespace the program drops all the tables it has created.

This program is not required with Oracle7 which delegates this task to the SMON process.

The program can be compiled using the proc.mk file supplied in the Pro*C demo directory. I have tested the program on:

    Sun-4       Sun0s 4.1.X
    Pyramid     OSx 5.1
    Pyramid     DC/OSx 1.1

    IBM RS6000 AIX 3.2
    NCR 3000 V.4 Rel 2.0  

Limitations:

  1. Does not accept command line arguments
  2. echos password
  3. Only does 1 tablespace at a time
  4. Assumes Oracle block size of 2K (this can be changed by recompilation - see source file)
  5. May need to increase dc_free_extents init.ora parameter

This program is NOT supported by Oracle, however I will attempt to deal with any suggestions, problems or enhancements you may have.

Enjoy

David



David Ruthven, Unix Post Sales Support, Oracle UK, The Ring, Bracknell RG12 1BW Email: druthven_at_uk.oracle.com

/*
  • PROGRAM: defrag.pc
    *
  • AUTHOR: David Ruthven (Oracle UK)
    *
  • DATE: 05-MAY-93
    *
  • STATUS: This program is unsupported.
    *
  • PURPOSE: Eliminate free extent fragmentation for a given
  • tablespace
    *
  • USAGE: Prompts for dba username, password, tablespace and
  • temporary table name template
    *
  • LIMITATIONS: 1. Does not accept command line arguments
  • 2. echos password
  • 3. Only does 1 tablespace at a time
  • 4. Assumes Oracle block size of 2K (this can be
  • changed by recompilation)
    *
    *
  • PLATFORMS: program compiles and runs on:
    *
  • Sun-4 Sun0s 4.1.X
  • Pyramid OSx 5.1
  • Pyramid DC/OSx 1.1
  • IBM RS6000 AIX 3.2
  • NCR 3000 V.4 Rel 2.0
    *
  • APPROACH:
    *
  • get name of tablespace to defragment
  • get temporary table name template
  • if any tables match template
  • request if ok to continue
  • LOOP
  • initialise variables
  • LOOP
  • identify largest contiguous extent
  • note if any adjacent free extents
  • END LOOP
  • IF there are adjacent free extents THEN
  • CREATE TABLE to occupy largest extent(s)
  • ELSE
  • EXIT
  • END LOOP
  • DROP all temporary tables

    */
/* ---< User configurable defines >---------------------------------------- */

#define ORA_BLK_SIZE 2048                     /* Oracle database block size */
#define TMP_TABLE "DEFRAG$TAB_" /* name template for temporary tables */
/* ---< End of User configurable defines >--------------------------------- */


#include <stdio.h>
#include <ctype.h>

#include <sys/types.h>
#include <time.h>
static  char    prog_name[] = "DEFRAG";
static  char    prog_stat[] = "Unsupported";
static  char    prog_ver[]  = "1.0";

#define SUCCESS         1
#define FAILURE         0

#define EXTENT struct extent_details

EXTENT {

    int     file_id;
    int     block_id;
    int     blocks;
    int     extents;

};
EXTENT  le;                 /* largest contiguous extent so far */
EXTENT  cl;                 /* current contiguous extent */

int     nxt_block_id;
int     extent_set;         /* true if >1 contig. free extents */
int     table_no;           /* no. of last created temporary table */


EXEC SQL INCLUDE SQLCA;
    /*



    The SQL Communications Area is a structure through which ORACLE makes run-     time status information such as error codes, warning flags, and diagnostic     text available to the program.

    */

EXEC SQL BEGIN DECLARE SECTION;
    /*



    All host variables used in embedded SQL must appear in the DECLARE SECTION.

    */
VARCHAR sql_stmt[512];
int     file_id;
int     block_id;
int     blocks;
int     extent_size;

VARCHAR tablespace[32];
VARCHAR table_name[32];
VARCHAR template[64];

VARCHAR user[32];
VARCHAR pswd[32];

EXEC SQL END DECLARE SECTION; void banner();

int     ora_connect();
int     declare_cursor();
int     defragment();
int     get_tablespace_name();
int     get_template();
int     create_table();
int     drop_tables();

void disconnect();
void show_error();
int asks();
void uppstr();

main()
{

    int status;

    banner(prog_name, prog_ver, prog_stat);     if (ora_connect() != SUCCESS)

        disconnect(FAILURE);
    if (get_tablespace_name() != SUCCESS)

        disconnect(FAILURE);
    if (get_template() != SUCCESS)

        disconnect(FAILURE);
    if (declare_cursor() != SUCCESS)

        disconnect(FAILURE);
    if (defragment() != SUCCESS)
        disconnect(FAILURE);

    status = drop_tables();
    disconnect(status);
}

/*


  • display banner showing program name, version and status

    */
    void banner(prog_name, prog_ver, prog_stat) char *prog_name, *prog_ver, *prog_stat; { time_t today; char *date_str;

    today = time((long *) 0);
    date_str = ctime(&today);
    printf("\n%s: Version %s - %s on %s",

            prog_name, prog_ver, prog_stat, date_str);     printf("\nClassification: AT USERS RISK (UNSUPPORTED)\n\n"); }

/*


  • prompt for password for Oracle DBA user
    *
  • connect to database

    */

ora_connect()
{

    int answer;

    answer = asks("DBA Username: ", user.arr);

    if (answer <= 0)

       return (FAILURE);

    user.len = strlen(user.arr);

    answer = asks("Password: ", pswd.arr);

    if (answer <= 0)

       return (FAILURE);

    pswd.len = answer;

    EXEC SQL WHENEVER SQLERROR GOTO SQL_ERROR;     EXEC SQL CONNECT :user IDENTIFIED BY :pswd;

    return (SUCCESS);

SQL_ERROR:
    {

        show_error("CONNECT");
        return (FAILURE);

    }
}

/*



FUNCTION: int declare_cursor()

ARGUMENTS: none

RETURN:     SUCCESS 
            FAILURE 

PURPOSE:    declare cursor to fetch rows from free extents list for
            the current tablespace.
        
APPROACH:   rows are returned in file# and block# order to make
            identification of rows for amalgamation a single-pass
            operation

TABLES:     

            SYS.DBA_FREE_SPACE

            Name                            Null?    Type
            ------------------------------- -------- ----
            TABLESPACE_NAME                 NOT NULL CHAR(30)
            FILE_ID                         NOT NULL NUMBER
            BLOCK_ID                        NOT NULL NUMBER
            BYTES                                    NUMBER
            BLOCKS                          NOT NULL NUMBER

 -----------------------------------------------------------------------------
*/

declare_cursor()
{

    EXEC SQL WHENEVER SQLERROR GOTO SQL_ERROR;     EXEC SQL DECLARE C1 CURSOR FOR

        SELECT      file_id
        ,           block_id
        ,           blocks
        FROM        sys.dba_free_space
        WHERE       tablespace_name = :tablespace
        ORDER BY    file_id, block_id;

    return (SUCCESS);

SQL_ERROR:
    {

        show_error("DECLARE CURSOR");
        return (FAILURE);

    }
}

defragment()
{

    for (;;)
    {

        EXEC SQL WHENEVER SQLERROR GOTO CURSOR_OPEN_ERROR;         EXEC SQL OPEN C1;

        EXEC SQL WHENEVER SQLERROR GOTO FETCH_ERROR;
        EXEC SQL WHENEVER NOT FOUND GOTO NO_FREE_EXTENTS;

        EXEC SQL FETCH C1
        INTO    :file_id,
                :block_id,
                :blocks;


        cl.file_id = file_id;
        cl.block_id = block_id;
        cl.blocks = blocks;
        cl.extents = 1;
            /* initialise current largest extent with first row */

        nxt_block_id = cl.block_id + cl.blocks;
            /* calculate block_id of next potential adjacent free extent */
        
        extent_set = 0;
        le.file_id = 0;
        le.block_id = 0;
        le.blocks = 0;
        le.extents = 1;
            /* initialise details for largest extent */

        for (;;)
        {
            EXEC SQL WHENEVER NOT FOUND GOTO END_OF_FETCH;
                /* no more rows */

            EXEC SQL FETCH C1
            INTO    :file_id,
                    :block_id,
                    :blocks;
                /* get next row */
    

            /*
             * locate largest contiguous
             * set of free extents
             */
            if (file_id == cl.file_id 
            &&  block_id == nxt_block_id)
            {
                /*
                 *  we can accumulate
                 */
                cl.blocks += blocks;
                cl.extents += 1;
                extent_set++;
                    /* more than one extent used */
            }
            else
            {
                /*
                 * cannot accumulate any more extents for this set
                 * check if this is now the largest free extent
                 */
                if (cl.blocks > le.blocks)
                {
                    le.block_id = cl.block_id;
                    le.blocks = cl.blocks;
                    le.extents = cl.extents;
                }
    
                cl.file_id = file_id;
                cl.block_id = block_id;
                cl.blocks = blocks;
                cl.extents = 1;
            }
    
            nxt_block_id = cl.block_id + cl.blocks;
        }

END_OF_FETCH:
        EXEC SQL WHENEVER NOT FOUND CONTINUE;
        EXEC SQL WHENEVER SQLERROR GOTO CURSOR_CLOSE_ERROR;

        EXEC SQL CLOSE C1;
        /*
         * check if this is now the largest free extent
         */
        if (cl.blocks > le.blocks)
        {
            le.block_id = cl.block_id;
            le.blocks = cl.blocks;
            le.extents = cl.extents;
        }

        /*
         * if there are no adjacent free extents then no more
         * defragmentation can be done
         */

        if (extent_set > 0)
        {
            if (create_table() == FAILURE)
                return(FAILURE);
        }
        else
        {
            EXEC SQL CLOSE C1;

            return (SUCCESS);
        }

    }

    EXEC SQL WHENEVER SQLERROR CONTINUE;     EXEC SQL WHENEVER NOT FOUND CONTINUE; NO_FREE_EXTENTS:
    {

        if (le.block_id == 0)
            printf("No free extents for tablespace '%s'\n", tablespace.arr);

        EXEC SQL CLOSE C1;

        return (SUCCESS);

    }

FETCH_ERROR:
    {

        show_error("FETCH");

        EXEC SQL CLOSE C1;         return (FAILURE);
    }

CURSOR_OPEN_ERROR:
    {

        show_error("OPENING CURSOR");
        return (FAILURE);

    }

CURSOR_CLOSE_ERROR:
    {

        show_error("CLOSING CURSOR");
        return (FAILURE);

    }
}

/*


  • prompt for tablespace name
    *


    */

get_tablespace_name()
{

    int answer;

    answer = asks("Tablespace name: ", tablespace.arr);

    if (answer <= 0)

       return (FAILURE);

    tablespace.len = answer;
    uppstr(tablespace.arr);

    return (SUCCESS);

}

/*


  • get template for temporary table names
    *
  • if some tables match
  • list names of tables which match template
  • if tables should not be dopped
  • then
  • exit
  • else
  • drop the tables
    *


    */

get_template()
{

    char    sql_1[64];
    char    sql_2[64];
    char    sql_3[64];

    char prompt[80];
    unsigned char yesno[32];
    int     answer;
    int     count;          /* count of table names matching template */

    sprintf(prompt, "Temporary table template [%s]: ", TMP_TABLE);

    answer = asks(prompt, template.arr);

    if (answer < 0)

       return (FAILURE);

    if (answer == 0)
    {

        /* user chose default */
        strcpy(template.arr, TMP_TABLE);
    }

    strcat(template.arr, "%");

        /* ensure template is a pattern stem for matching table names */

    template.len = answer;
    uppstr(template.arr);

    EXEC SQL WHENEVER SQLERROR GOTO SQL_ERROR;     /*


  • build sql statement to select table
  • names matching template
    */ strcpy(sql_1, "select table_name from sys.dba_tables"); sprintf(sql_2, "where tablespace_name = '%s'", tablespace.arr); sprintf(sql_3, "and table_name like '%s'", template.arr);

    sprintf(sql_stmt.arr, "%s %s %s", sql_1, sql_2, sql_3);     sql_stmt.len = strlen(sql_stmt.arr);

    /*
     *  PREPARE, DECLARE, OPEN
     */

    EXEC SQL PREPARE S3 FROM :sql_stmt;
    EXEC SQL DECLARE C3 CURSOR FOR S3;
    EXEC SQL OPEN C3;     EXEC SQL WHENEVER NOT FOUND GOTO END_OF_FETCH;

    /*
     *  display matching table names
     */

    for (count=0;;count++)
    {

        EXEC SQL FETCH C3 INTO :table_name;

        table_name.arr[table_name.len] = '\0';

        if (count == 0)
            printf("The following table name(s) match the template:\n");

        printf("\t%s\n", table_name.arr);
    }

END_OF_FETCH:
    EXEC SQL WHENEVER NOT FOUND CONTINUE;     EXEC SQL CLOSE C3;     EXEC SQL WHENEVER SQLERROR CONTINUE;     EXEC SQL WHENEVER SQLWARNING CONTINUE;     if (count > 0)
    {

        answer = asks("\nOk to drop these tables [Yes] ", yesno);

        uppstr(yesno);
        if (strncmp(yesno, "YES", strlen(yesno)) == 0)
            return (drop_tables());
        return (FAILURE);

    }

SQL_ERROR:
    {

        EXEC SQL CLOSE C3;

        if (sqlca.sqlcode == 0)
        {
            /* no table names matched template */
            return (SUCCESS);
        }

        show_error("GET TEMPLATE");
        return (FAILURE);

    }
    return (SUCCESS);

        /* should never reach here */
}

/*


  • create table to occupy largest free extent

    */

create_table()
{

    char    sql_1[64];
    char    sql_2[64];
    char    sql_3[64];

    EXEC SQL WHENEVER SQLERROR GOTO SQL_ERROR;     EXEC SQL WHENEVER NOT FOUND CONTINUE;     table_no++;
    extent_size = le.blocks * ORA_BLK_SIZE;

    sprintf(sql_1, "create table %s%d (afield char(1))", TMP_TABLE, table_no);
    sprintf(sql_2, "tablespace %s", tablespace.arr);
    sprintf(sql_3, "storage (initial %d)", extent_size);

    sprintf(sql_stmt.arr, "%s %s %s", sql_1, sql_2, sql_3);     sql_stmt.len = strlen(sql_stmt.arr);

    printf("Coalescing extent of %3d fragments, %3d blocks\n",

            le.extents, le.blocks);

    /*
     * create table to use up largest extent
     */

    EXEC SQL EXECUTE IMMEDIATE :sql_stmt;

    return (SUCCESS);

SQL_ERROR:
    {

        show_error("CREATE TABLE");
        return (FAILURE);

    }
}

/*


  • drop all temporary tables created by defrag

    */

drop_tables()
{

    char    sql_1[64];
    char    sql_2[64];
    char    sql_3[64];

    EXEC SQL WHENEVER SQLERROR GOTO SQL_ERROR;

    /*
     * build sql statement to select names of 'temporary' tables
     */

    strcpy(sql_1, "select table_name from sys.dba_tables");     sprintf(sql_2, "where tablespace_name = '%s'", tablespace.arr);     sprintf(sql_3, "and table_name like '%s'", template.arr);

    sprintf(sql_stmt.arr, "%s %s %s", sql_1, sql_2, sql_3);     sql_stmt.len = strlen(sql_stmt.arr);

    /*
     *  PREPARE, DECLARE, OPEN
     */

    EXEC SQL PREPARE S2 FROM :sql_stmt;
    EXEC SQL DECLARE C2 CURSOR FOR S2;
    EXEC SQL OPEN C2;     EXEC SQL WHENEVER NOT FOUND GOTO END_OF_FETCH;

    /*
     *  drop each table retrieved
     */

    for (;;)
    {

        EXEC SQL FETCH C2 INTO :table_name;

        table_name.arr[table_name.len] = '\0';

        sprintf(sql_stmt.arr, "drop table %s", table_name.arr);
        sql_stmt.len = strlen(sql_stmt.arr);

        /*
         * drop the table
         */
        EXEC SQL EXECUTE IMMEDIATE :sql_stmt;

    }

END_OF_FETCH:
    EXEC SQL WHENEVER NOT FOUND CONTINUE;     {

        EXEC SQL CLOSE C2;         return (SUCCESS);
    }
SQL_ERROR:
    {

        show_error("DROP TABLE");
        return (FAILURE);

    }
}

/*


  • commit if no problems encountered, otherwise rollback
    *
  • disconnect from database
    *
  • exit with appropriate status (1 = error, 0 = ok)

    */

void
disconnect(status)
{

    int exit_status;

    EXEC SQL WHENEVER SQLERROR CONTINUE;     EXEC SQL WHENEVER SQLWARNING CONTINUE;     if (status == FAILURE)
    {

        EXEC SQL ROLLBACK RELEASE;
        if (sqlca.sqlcode != 0)
            show_error("ROLLBACK RELEASE");
        exit_status = 1;

    }
    else
    {
        EXEC SQL COMMIT RELEASE;
        if (sqlca.sqlcode != 0)
            show_error("COMMIT RELEASE");
        exit_status = 0;

    }
 exit (exit_status);
}

/*


  • display prompt and error returned by SQL statement

    */

void
show_error(str)
char *str;
{

    printf("%s %-70.70s\n", str, sqlca.sqlerrm.sqlerrmc); }

/*


  • print the 'text' on STDOUT and read up to 'len' characters into
  • the buffer pointed to by variable from STDIN.
    *
  • text points to the null terminated string to be printed
  • variable points to a buffer of at least 'len'+1 characters
    *
  • asks returns the number of character read into the string, or a
  • -1 if -eof- was encountered

    */

int
asks(text,variable)
char text[];
unsigned char variable[];
{

   printf(text);
   return ( gets(variable) == (char *)0 ? EOF : strlen(variable) ); }

/*


  • convert string, in place, to upper case

    */

void
uppstr(str)
unsigned char *str;
{

    unsigned char *p;

    for (p=str; *p; p++)

        if (islower(*p))
            *p = toupper(*p);

} Received on Wed May 05 1993 - 16:44:57 CEST

Original text of this message