The Oracle (tm) Users' Co-Operative FAQ

How can I check if I have the right indexes for the foreign key constraints on a child table ?

Author's name: Connor McDonald

Author's Email:

Date written: June 28th 2001

Oracle version(s): 7.3+

Throughout different versions of Oracle, there have been issues (particularly locking ones) with having foreign key columns being left unindexed. Here is a script to reveal what reparative action (if any) you may need to take.

Back to index of questions

Earlier versions of Oracle 7 had quite significant restrictions on what operations could be done to parent tables in a foreign key relationships if the appropriate indexes did not exist. Virtually any parent operation on an unindexed foreign key would lock the child table. Combined with the fact that most CASE tools at the time did not generate foreign key indexes, many sites had disasterous problems with locking, which of course ran in direct contradiction to the reasons that they were sold on Oracle in the first place - namely, row level locking.

This led to a (still commonly adopted) policy of indexing every foreign key. However, Oracle has improved their game with each subsequent release, and now you only really need to have the index if you are expecting UPDATEs or DELETEs on the parent table (including DELETE CASCADE) in conjunction with attempts to modify the underlying child table.

Since any index always add some space and resource overhead, it is probably unwise to blindly index every foreign key. But, if there are legitimate locking or query performance benefits to be made, the following script authored by Tom Kyte gives a list of foreign keys, their columns and a flag indicating whether the appropriate index exists.

column columns format a20 word_wrapped
column table_name format a30 word_wrapped

select decode( b.table_name, NULL, '****', 'ok' ) Status, 
	   a.table_name, a.columns, b.columns
( select substr(a.table_name,1,30) table_name, 
		 substr(a.constraint_name,1,30) constraint_name, 
	     max(decode(position, 1,     substr(column_name,1,30),NULL)) || 
	     max(decode(position, 2,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position, 3,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position, 4,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position, 5,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position, 6,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position, 7,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position, 8,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position, 9,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position,10,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position,11,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position,12,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position,13,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position,14,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position,15,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(position,16,', '||substr(column_name,1,30),NULL)) columns
    from user_cons_columns a, user_constraints b
   where a.constraint_name = b.constraint_name
     and b.constraint_type = 'R'
   group by substr(a.table_name,1,30), substr(a.constraint_name,1,30) ) a, 
( select substr(table_name,1,30) table_name, substr(index_name,1,30) index_name, 
	     max(decode(column_position, 1,     substr(column_name,1,30),NULL)) || 
	     max(decode(column_position, 2,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position, 3,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position, 4,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position, 5,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position, 6,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position, 7,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position, 8,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position, 9,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position,10,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position,11,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position,12,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position,13,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position,14,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position,15,', '||substr(column_name,1,30),NULL)) || 
	     max(decode(column_position,16,', '||substr(column_name,1,30),NULL)) columns
    from user_ind_columns 
   group by substr(table_name,1,30), substr(index_name,1,30) ) b
where a.table_name = b.table_name (+)
  and b.columns (+) like a.columns || '%'

Note that as of Oracle 8, you can have more than 16 columns in an index and/or related constraint, but I would humbly suggest that if you have more than 16 columns in an index, then you may have other issues besides knowing what foreign keys are indexed...

Further reading:

Back to top

Back to index of questions