The Oracle (tm) Users' Co-Operative FAQ

Which of my indexes are redundant and can be dropped ?


Author's name: Svend  Jensen

Author's Email: Svend@OracleCare.Com

Date written: Jan. 2002

Oracle version(s): 8i (tested on Win2k, 8.1.7)

Many a database drag along with unused or lightly used indexes, serving long forgotten purposes. They add an unwished for extra workload to a critical resource.

Two methods came to my mind targeted at nailing down passive, resource consuming indexes. This is not a 100% solution. This is not a point and click - problem fixed solution. Results have to be evaluated and (hard) decisions have to be made. The benefits and (might be) trade-offs have to be evaluated.

This is the first solution, working through the buffer cache i.e. making use of the v$bh view and other sys objects.

Some background: Databases are alive and evolving as long as the data they hold are of value to the company that owns the resource. Databases change over time, as they reflect the business they support. Tables, indexes, procedures, packages, reports, user interfaces and so forth are added, modified over time to reflect the business needs. As time goes by, the complexity and dependency increases. As people also have a tendency to move on in in their life, knowledge about a certain created 'devices' is not available any more. And few of us have the guts or time to do a real cleanup in the database and associated file systems, and drop or delete what we think - is no longer in use. Hence we drag around with indexes (and tables,...) that serve long forgotten purposes.

Back to index of questions


The idea is to exclude indexes in active use from all the indexes in the system. And then check the leftovers to see if they have any use or value (this is the more manual part).

For Oracle versions prior to 9i, we can only se if an index has been in use in a given time frame. From a given number of indexes in a system, we can measure if a given index has been in use in the time frame we spend to analyze and measure. This then excludes the 'once a year' use of an index for reporting numbers from the general ledger annual report of something. And maybe we were better of, creating the index when called for, and drop it when the task has completed.

First goal is to find indexes that are actually in use, for purposes other than being maintained by dml's. I have chosen two ways to solve this. This first part is through the use of buffer cache (db_block_buffers) and a following part (still in work) is throughout the use of  library cache in shared pool (i.e.. v_$sql et.al.)

Buffer cache solution:

If an index is in use for active lookup, at least some blocks must show up in the buffer_cache, and remain there until aged out. If you don't use all tree buffer_caches, and have a newer database version, this is not very complicated to solve. Just takes some time. Create a keep or recycle pool of decent size, and target the suspect  indexes for this pool. Ie. alter index X storage(buffer_pool [keep|recycle]). After a while (hours or more), check if the targeted indexes is present in the pool at all or with a large percentage of dirty blocks. If that is the case, evaluate if the index can be dropped (seems likely).

If you use all tree buffer caches, or run an older version, here is a more generic solution. It goes: Select all indexes, total number of blocks in the buffer_caches, number of dirty blocks and time stamp per index and store this as a table. Create a view designed for update the table, and a stored procedure to do it. Run the stored procedure regularly to update the table. A small routine for this is supplied. The procedure updates the result table a time stamp and the increased number of blocks, if the total number or number of dirty blocks of a given index has increased. Run the procedure every say 5-15 minutes for a day more, depending on how fragile your buffer_caches is and on db usage pattern. Then check the table. Don't do any online index rebuilds, or anything  that is likely to disturb your metering tool. If this cant be avoided, clear metering values in the table and/or restart this procedure.
In case that the index hasn't had any blocks (recorded) in the buffer_caches, you can most likely drop it. It might be one of these 'once a period' indexes. Take appropriate action if that's the case. Or it might be that the underlying table is never accesses. Export table and all definitions, then rename or drop. Some evaluation is needed here.

If the mayor part of blocks of a given index are dirty, it is likely that is is only present in the buffer cache due to dml's.. Evaluate if the index can be dropped and record the index definition, drop or invalidate the index (if large).

Run the script (might have to change tablespaces). Run the procedure oci_index_cache_block_prc or supplied loop as fitting. When done, evaluate.

If you have lots of schema's and/or a large complex setup, use the commented code to evaluate schemes, one by one. The code here can have negative impact on busy systems.

/****************************************************************************************
  This program is free ware under terms of GNU's General Public License®
  and Open Source Foundation® as long as the copyright notice is not removed.
      Sysdate:         January 2002
      Username:        © 2002 Svend Jensen, Svend@OracleCare.Com

Rem
Rem     ==========================================================================
Rem     DISCLAIMER:
Rem     This script is provided for Oracle DBAs "AS IS". It is NOT supported
Rem     by author, Oracle World Wide Technical Support nor third parties.
Rem     The script has been tested and appears to work as intended.
Rem     NO responsibility taken for working nor use, no matter the circumstances.
Rem     You should always run new scripts on a test instance initially.
Rem     ===========================================================================
Rem     # Improvements and extensions are welcome.
******************************************************************************************/
    -- connect sys/<password> [as sysdba]
    -- creating base table for index usage investigation
    -- can be global temporary if you like. But can give problems with commit.
    -- Created: Svend Jensen 2002
    -- remove old stuff
    drop index oci_index_cache_block_udx
    ;
    drop view  oci_index_cache_block_vw
    ;
    drop table oci_index_cache_block_use
    ;
    -- now (re)create table, view, index and procedure
    create table oci_index_cache_block_use
    tablespace tools
    as
    select /*+ all_rows */ 
    object.owner, object.obj# index_obj#, object.index_name, object.table_obj#, object.table_name, 
    sum(decode(bhead.dirty,'Y',1,0)) dirty#datablock, 
    count(bhead.obj#) total#datablock, sysdate timestamp 
    from 
        (select  /*+ all_rows */ 
         u.name owner, o.obj#, o.name index_name, i.bo# table_obj#, o2.name table_name 
         --  i.bo# is table obj# for index row in ind$ 
         from   obj$ o, ind$ i, obj$ o2, user$ u
         where  o.obj#     = i.obj#
         and    o2.obj#    = i.bo#
         and    o.dataobj# = i.dataobj# 
         and    o.owner#   = u.user#
         --  and    o.owner#   = (select user# from user$ where name = upper('$user_name')) -- 
         --  uncommect if only user/schema owner to be checked, and fill in $user_name --   
        )      
        object,
        (select  objd as obj#, dirty
         from v_$bh  
         where  status != 'free'
        )       
        bhead
        where object.obj#  = bhead.obj#(+) 
        group by object.owner, object.obj#, object.index_name, object.table_obj#, object.table_name
        order by total#datablock desc
        ;

       -- for update of the base table with new or change entries found in buffer_cache
        create or replace view oci_index_cache_block_vw
       (owner, index_obj#, index_name, table_obj#,
        table_name, dirty#datablock,
        total#datablock, timestamp)
       as (
           select /*+ all_rows */ 
           object.owner, object.obj# index_obj#, object.index_name, object.table_obj#, object.table_name, 
           sum(decode(bhead.dirty,'Y',1,0)) dirty#datablock, 
           count(bhead.obj#) total#datablock, sysdate timestamp 
           from 
                (select  /*+ all_rows */ 
                 u.name owner, o.obj#, o.name index_name, i.bo# table_obj#, o2.name table_name 
                 --  i.bo# is table obj# for index row in ind$ 
                 from   obj$ o, ind$ i, obj$ o2, user$ u
                 where  o.obj#     = i.obj#
                 and    o2.obj#    = i.bo#
                 and    o.dataobj# = i.dataobj# 
                 and    o.owner#   = u.user#
                 --  and    o.owner#   = (select user# from user$ where name = upper('$user_name')) -- 
                 --  uncommect if only user/schema owner to be checked, and fill in $user_name --   
                )      
                object,
                (select  objd as obj#, dirty
                 from v_$bh  
                 where  status != 'free'
                )       
                bhead
                where object.obj#  = bhead.obj# -- (+): select only hits in buffer_cache 
                group by object.owner, object.obj#, object.index_name, 
                object.table_obj#, object.table_name
                ) with check option
       ;

       -- create index on table oci_index_cache_block_use for updating
       create unique index oci_index_cache_block_udx
       on oci_index_cache_block_use
       (owner, index_obj#, index_name, table_obj#, table_name)
       compute statistics 
       tablespace INDX  
       ;


       -- create update procedure
      create or replace procedure oci_index_cache_block_prc
      as
      /* variables */
      v_owner              varchar2(30) ;
      v_index_obj#         number       ;
      v_index_name         varchar2(30) ;
      v_table_obj#         number       ;
      v_table_name         varchar2(30) ;
      v_dirty#datablock    number       ;
      v_total#datablock    number       ;
      v_timestamp          date         ;
      /* cursors */
      cursor cur_oci_index is select * from OCI_INDEX_CACHE_BLOCK_VW ;
      BEGIN 
      open   cur_oci_index ;
      fetch  cur_oci_index into  v_owner, v_index_obj#, v_index_name, v_table_obj#,
                                 v_table_name, v_dirty#datablock,
                                 v_total#datablock, v_timestamp ;
     while cur_oci_index%FOUND
     LOOP
         update OCI_INDEX_CACHE_BLOCK_USE 
         set  total#datablock = v_total#datablock, 
              timestamp        = v_timestamp
         where owner           = v_owner
         and   index_obj#      = v_index_obj#
         and   index_name      = v_index_name 
         and   table_obj#      = v_table_obj#
         and   table_obj#      = v_table_obj#  
         and   total#datablock < v_total#datablock 
         ;

        update OCI_INDEX_CACHE_BLOCK_USE 
        set  dirty#datablock  = v_dirty#datablock, 
        timestamp        = v_timestamp
        where owner           = v_owner
        and   index_obj#      = v_index_obj#
        and   index_name      = v_index_name 
        and   table_obj#      = v_table_obj#
        and   table_obj#      = v_table_obj#  
        and   dirty#datablock < v_dirty#datablock
        ;                  
         
        fetch  cur_oci_index into  v_owner, v_index_obj#, v_index_name, v_table_obj#,
                                   v_table_name, v_dirty#datablock,
                                   v_total#datablock, v_timestamp ;
        END LOOP ;
        commit ;
        close cur_oci_index ;
   
     EXCEPTION
          WHEN OTHERS THEN
           IF cur_oci_index%ISOPEN THEN
                   close cur_oci_index ;
              END IF ;
              dbms_output.put_line ('Others execption in oci_index_cache_block_prc '||SQLERRM) ;
              RAISE ;

     END ;
     /
/**************** a little update loop **************************
    begin
    for j in 1..30 loop
   oci_index_cache_block_prc ;
   dbms_lock.sleep(300) ; -- sleep 5 minutes
   end loop ;
  end ;
  /
*********************************************************/

End of story - have fun.


Further Reading: Oracle concepts manual, http://www.oracle.com http://technet.oracle.com , http://metalink.oracle.com

This question is also addressed by the following documents:

Author Title/URL Suggested by Referee's comments
Howard Rogers Are my indexes useful ? Jonathan Lewis A short pdf file largely about a new 9i feature. You will need Adobe Acrobat to read this document.

NOTE - The referenced site was closed down by Oracle Australia. in May 2002. This link has been maintained in case Howards gets permission to re-open the site.

       

Back to top

Back to index of questions