Index usage


To see what indexes you can remove from a database, you first need to know which ones are typically in use. Scanning the buffer cache (X$BH) is a good starting point to see which indexes are in the cache - but its important to take into account that index blocks need to be present in the buffer cache just to keep the indexes updated, thus their presence in the buffer cache is not a firm guarantee of their necessity.

A next step is to see if any sql's use those indexes. The procedure below does this by issuing an explain plan against every sql presently in the cache. It then extracts any rows representing index access from the PLAN_TABLE entries created and stores then in the INDEX_LIST table below. When run at regular intervals, it also updates a detection count for each index, representing that it was found at least once in this run.

create table index_list
 ( OBJECT_OWNER    VARCHAR2(30),
   OBJECT_NAME     VARCHAR2(30),
   DETECTION_COUNT NUMBER(10)
);


create or replace 
procedure index_access_list is
 cursor c_sql is
   select s.parsing_user_id, st.address||st.hash_value sqlhash, st.sql_text
   from sys.v_$sql s, sys.v_$sqltext st
   where st.address = s.address
   and st.hash_value = s.hash_value
   and st.command_type in (2,3,6,7)
   and s.parsing_user_id > 0
   order by st.address, st.hash_value, st.piece;
  prev_sql varchar2(80);
  prev_uid number := 0;
  entire_sql varchar2(32767);
  err_cnt number := 0;
  tot_cnt number;
  v_user varchar2(30);
begin
delete from plan_table;
commit;
for i in c_sql loop
  if prev_uid != i.parsing_user_id then 
    select name into v_user
    from sys.user$
    where user# = i.parsing_user_id; 
    execute immediate 'alter session set current_schema = '||v_user;
  end if;
  tot_cnt := c_sql%rowcount;
  if prev_sql != i.sqlhash then
    begin
      execute immediate 
        'explain plan set statement_id = '''||i.sqlhash||''' '||
        ' into plan_table'||
        ' for '||entire_sql;
    exception when others then err_cnt := err_cnt + 1;
    end;
    entire_sql := i.sql_text;
  else
    entire_sql := entire_sql || i.sql_text;
  end if;
  prev_sql := i.sqlhash;
end loop;
dbms_output.put_line('Statements processed: '||tot_cnt);
dbms_output.put_line('Statements failed:    '||err_cnt);
update index_list 
set detection_count = detection_count + 1
where (object_owner, object_name) in  
 ( select object_owner, object_name
   from plan_table
   where operation = 'INDEX' );
insert into index_list
select distinct object_owner, object_name, 1
from ( 
  select object_owner, object_name
  from plan_table
  where operation = 'INDEX'
  minus
  select object_owner, object_name
  from index_list );
commit;
select username into v_user
from sys.v_$session
where audsid = userenv('SESSIONID'); 
execute immediate 'alter session set current_schema = '||v_user;
end;
/