You are on page 1of 9

UNUSABLE INDEXES Author JP Vijaykumar

It is beyond the scope of this document to present all the scenarios that mark an index as unusable and how to validate unusable indexes. We are going to see, what happens to an index, when it becomes unusable. Before proceeding further, let us find, what is an index. An index on a table is an object, that stores the table's column values in a sorted order (by default ascending order) along with the corresponding rowids. The column sort order can be specified in the index creation script as: create index temp_jp_idx on temp_jp(col1 asc); create index temp_jp_idx on temp_jp(col1 desc); What is a rowid? A rowid is a pseudo column , that uniquely identifies a row within a table. A rowid represents the physical location of the corresponding row in the table. The format of a rowid is <object id><file id><block id><row number>, and is represented as OOOOOOFFFBBBBBBRRR (numbers 6,3,6,3). This is the Extended rowid format from 9i onwards. If a table is altered through a move command, why the indexes on a table become unusable? If the physical location of the tables data change during an operation, then Oracle marks the indexes on the table as unusable. The current rowids of the tables data, after the move, point to new location. The old rowids in the index, from before the move command still point to the old location, where there is no data. Oracle can not access data using an index with invalid rowids. As such Oracle marks all the indexes on a table as unusable after the move command. To analyze the issue, I simulated a test case. drop table temp_jp; drop table temp_rowids_jp; create table temp_jp(col1 number,col2 varchar2(20)) tablespace users1;

insert into temp_jp values(1,'RAMA'); insert into temp_jp values(3,'SITA'); insert into temp_jp values(2,LAKSHMAN); commit; set autot on exp select * from temp_jp where col1=3; COL1 COL2 ---------- -------------------3 SITA Execution Plan ---------------------------------------------------------0 1 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=1 Bytes=25) 0 TABLE ACCESS (FULL) OF 'TEMP_JP' (TABLE) (Cost=3 Card=1 Bytes=25)

Oracle performed a full table scan on temp_jp table, as there is no index on the table. Created an index on temp_jp table. create index temp_jp_idx on temp_jp(col1 asc); set autot on exp select * from temp_jp where col1=3; COL1 COL2 ---------- -------------------3 SITA Execution Plan ---------------------------------------------------------0 1 2 1 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=2 Card=1 Bytes=25) 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEMP_JP' (TABLE) (Cost=2 Card=1 Bytes=25) INDEX (RANGE SCAN) OF 'TEMP_JP_IDX' (INDEX) (Cost=1 Card =1)

Now Oracle accessed temp_jp tables data using the temp_jp_idx index. select index_name, status from dba_indexes where table_name='TEMP_JP'; INDEX_NAME STATUS

------------------------------ -------TEMP_JP_IDX VALID This temp_jp_idx is valid and Oracle is using this index while accessing temp_jp tables data. Find the current rowids of temp_jp tables data. select rowid,a.* from temp_jp a; ROWID COL1 COL2 1 RAMA 3 SITA 2 LAKSHMAN ------------------ ---------- -------------------AAA16tAAEAAAADoAAA AAA16tAAEAAAADoAAB AAA16tAAEAAAADoAAC

I will preserve the current rowids from temp_jp table before the move for future reference. create table temp_rowids_jp as select col1,rowid as before_move_rowid from temp_jp; Find the object id, file id, block id and row number from the rowid: select dbms_rowid.rowid_object(rowid) "object", dbms_rowid.rowid_relative_fno(rowid) "file", dbms_rowid.rowid_block_number(rowid) "block", dbms_rowid.rowid_row_number(rowid) "row" from temp_jp / object 220845 220845 220845 file 4 4 4 block 232 232 232 row 0 1 2 ---------- ---------- ---------- ----------

Let us find the object owner, object name and object type from dba_object for the object id 220845. select object_id,owner, object_name, object_type,created from dba_objects where object_id = 220845; OBJECT_ID OWNER OBJECT_NAME --------220845 -------------------SCOTT TEMP_JP OBJECT_TYPE ------------------TABLE CREATED --------28-FEB-08

select object_id,owner,object_name,object_type,created from dba_objects where created > trunc(sysdate) -1/24; OBJECT_ID OWNER -------------OBJECT_NAME --------------OBJECT_TYPE ------------------CREATED ---------

220846 220845 220847

SCOTT TEMP_JP_IDX SCOTT TEMP_JP

INDEX TABLE

28-FEB-08 28-FEB-08 28-FEB-08

SCOTT TEMP_ROWIDS_JP TABLE

select object_id,owner,object_name,object_type,created from dba_objects where object_id in (select max(object_id) from dba_objects); OBJECT_ID OWNER OBJECT_NAME --------220847 -------------------OBJECT_TYPE CREATED ------------------- --------28-FEB-08

SCOTT TEMP_ROWIDS_JP TABLE

Let us query the temp_jp tables data using the current rowid. select * from temp_jp where rowid = 'AAA16tAAEAAAADoAAB'; COL1 COL2 ---------- -------------------3 SITA The rowids are correctly pointing to the location of the temp_jp tables rows. The index temp_jp_idx on emp_jp table is having the right rowids for the tables data. The object_id from the rowid is converting to the right table. We can access the tables data using the rowids. Now let us move the table to a different tablespace and see. alter table temp_jp move tablespace users2; SQL> select * from temp_jp where rowid = 'AAA16tAAEAAAADoAAB'; select * from temp_jp where rowid = 'AAA16tAAEAAAADoAAB' * ERROR at line 1: ORA-01410: invalid ROWID After the table move, the rowids changed. The old rowids no longer point to the actual tables data. My select query on temp_jp table using rowid worked before the move command and failed after the move command. Let us find the current rowid for the temp_jp tables data, after the table is altered with a move command. select rowid,a.* from temp_jp a; ROWID -----------------COL1 COL2 ---------- --------------------

AAA16wAAMAAAC+MAAA AAA16wAAMAAAC+MAAB AAA16wAAMAAAC+MAAC

1 3 2

RAMA SITA LAKSHMAN

The rowids in temp_jp table changed after the move command. From the definition of rowid, we observe that rowid consists of <object_id><file_id><block_id><row_num>. Since the table moved from users1 tablespace to users2 tablespace, the physical attributes of the rows, like file_id and block_id changed. Hence the rowids in temp_jp table changed. Let us find the object_id, file_id, block_id and rownumber from the new rowids of the table temp_jp after the move command: select dbms_rowid.rowid_object(rowid) "object", dbms_rowid.rowid_relative_fno(rowid) "file", dbms_rowid.rowid_block_number(rowid) "block", dbms_rowid.rowid_row_number(rowid) "row" from temp_jp / object 220848 220848 220848 file 12 12 12 block 12172 12172 12172 row 0 1 2 --------- ---------- ---------- ----------

After the move command, Oracle assigned a new object_id to the table. The file number is 12 that belong to the users2 tablespace. The current block number is 12172. The rownumbers remained the same. The move command has moved the tables location, did not change the rows order inside the table. Let us find the object details using the new object_id: select object_id,owner, object_name, object_type,created from dba_objects where object_id = 220848; no rows selected The objectid from the new rowid after the move command is not retrieving the objects details. Instead the object id for the table temp_jp from dba_objects view, is displaying the old object_id from before the move command. select object_id,owner, object_name, object_type,created from dba_objects where object_name = 'TEMP_JP'; OBJECT_ID OWNER OBJECT_NAME --------220845 -------------------SCOTT TEMP_JP OBJECT_TYPE ------------------TABLE CREATED --------28-FEB-08

This might be a bug in Oracle.

I want to find the objects that were created in the past one hour: select object_id,owner,object_name,object_type,created from dba_objects where created > trunc(sysdate) -1/24; OBJECT_ID OWNER OBJECT_NAME --------220846 220845 220847 -------------------SCOTT TEMP_JP_IDX SCOTT TEMP_JP OBJECT_TYPE ------------------INDEX TABLE CREATED --------28-FEB-08 28-FEB-08 28-FEB-08

SCOTT TEMP_ROWIDS_JP TABLE

select object_id, owner, object_name, object_type, created from dba_objects where object_id in (select max(object_id) from dba_objects); OBJECT_ID OWNER OBJECT_NAME --------220847 ------------------OBJECT_TYPE ------------------CREATED --------28-FEB-08

SCOTT TEMP_ROWIDS_JP TABLE

Let us query the table temp_jp using the new rowid: select * from temp_jp where rowid = AAA16wAAMAAAC+MAAB; COL1 COL2 ---------- -------------------3 SITA We could query data from the table using the new rowid. But the objectid derived from the new rowid is not retrieving the object details. From this we observe that Oracle is not using the objectid from the current rowid to retrieve the tables data. Oracle used the fileid, blockid and row number to access the data. Let us see the status of our index on temp_jp table after the move command. select index_name, status from dba_indexes where table_name='TEMP_JP'; INDEX_NAME TEMP_JP_IDX set autot on exp select * from temp_jp where col1=3; COL1 COL2 ---------3 -------------------SITA STATUS UNUSABLE ------------------------------ --------

Execution Plan ---------------------------------------------------------0 1 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=2 Card=1 Bytes=25) 0 TABLE ACCESS (FULL) OF 'TEMP_JP' (TABLE) (Cost=2 Card=1 Bytes=25)

As the existing index on temp_jp table is unusable, a full table scan was performed on the table. As you remember, the table temp_jps index still contains invalid rowids Now let us see, whether we can select data using the old rowids of the table temp_jp SQL> select * from temp_jp where rowid = 'AAA16tAAEAAAADoAAB'; select * from temp_jp where rowid = 'AAA16tAAEAAAADoAAB' * ERROR at line 1: ORA-01410: invalid ROWID The old rowids are no longer pointing to the correct location of the tables rows. Our index temp_jp_idx is still having the old rowids from before the move command, which are invalid. As such Oracle marked the index as unusable. But we can access the tables data using the new rowids select * from temp_jp where rowid = 'AAA16wAAMAAAC+MAAB'; COL1 COL2 ---------- -------------------3 SITA The index temp_jp_idx should be re-created with correct rowids, to make it valid. Let us compare the rowids of the table temp_jp before and after the move command: select a.col1, a.before_move_rowid,b.rowid after_move_rowid from temp_rowids_jp a, temp_jp b where a.col1 = b.col1; COL1 BEFORE_MOVE_ROWID --------- -----------------1 3 2 AFTER_MOVE_ROWID ------------------

AAA16tAAEAAAADoAAA AAA16wAAMAAAC+MAAA AAA16tAAEAAAADoAAB AAA16wAAMAAAC+MAAB AAA16tAAEAAAADoAAC AAA16wAAMAAAC+MAAC

As you might have noticed, after the move command, the table is moved to a different tablespace. And the objectid, fileid and blockid changed. Since the data within the table did not change during the move command, the row numbers remain the same.

During the move command, the table temp_jp is recreated into users2 tablespace from the earlier tablespace users1. We had noticed the same from the file number 12 and block number 12172. As such there is no data in the old location at file number 4 and block number 232. Our index temp_jp_idx, still contains the old rowids from before the move command, which are not pointing to the correct location of tables data. Hence, Oracle marks the index as unusable. To repair the unusable index, rebuild the index. alter index temp_jp_idx rebuild online; select index_name, status from dba_indexes where table_name='TEMP_JP'; INDEX_NAME -----------------------------TEMP_JP_IDX STATUS -------VALID

After rebuilding the unusable index, the index is validated. set autot on exp select * from temp_jp where col1=3; COL1 COL2 ---------- -------------------3 SITA

Execution Plan ---------------------------------------------------------0 1 2 1 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=2 Card=1 Bytes=25) 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEMP_JP' (TABLE) (Cost=2 Card=1 Bytes=25) INDEX (RANGE SCAN) OF 'TEMP_JP_IDX' (INDEX) (Cost=1 Card =1)

After rebuilding the index, my select queries on the table temp_jp are using the index. Remember, when you move a table, the index on the table becomes unusable. But when an index is moved to a different physical location with a rebuild command, the index never becomes unusable, why? This test case was generated on a Oracle 10.2.0.3.0 database. Reference

Metalink Note:1057891.6 How to use dbms_rowid procedure and functions Metalink Note:1054736.6 How indexes become unusable? Metalink Note:165917.1 Common maintenance commands that make index unusable Metalink Note:222769.1 Description and default values of SQL*Loader parameters Metalink Note:281500.1 SKIP_UNUSABLE_INDEXES initialization Parameter in Oracle Database 10g Metalink Note:72512.1 Initialization Parameters Metalink Note:1054736.6 How do indexes become index unusable? Metalink Note:399410.1 Rowid Structure and Base-64 Conversion Metalink Note:139707.1 "Update Global Indexes" Allows Automatic Global Index Maintenance During DDL http://www.mydigitallife.info/2006/01/28/ora-01502-oracle-index-in-unusable-state/ http://www.dba-oracle.com/t_indexes_invalid_unusable.htm http://www.oracledba.co.uk/tips/truncate_unusable.htm http://www.adp-gmbh.ch/ora/concepts/rowid.html http://thinkoracle.blogspot.com/2007_05_01_archive.html http://zengin.wordpress.com/2007/07/19/rowid-format/ http://www.psoug.org/reference/pseudocols.html http://www.dba-village.com/village/dvp_forum.OpenThread?ThreadIdA=31587 http://www.unix.com.ua/orelly/oracle/bipack/ch09_01.htm#ch09-21687 http://www.directions.com.au/articlehtmlxid_149

You might also like