Tuesday, January 15, 2013

Don’t forget to index your Oracle foreign keys

This is another note-to-myself blog post. Like PostgreSQL, Oracle does not create indexes on foreign key columns. This can lead to both poor performance and deadlocks. This is because when the parent table’s column where the foreign key points to is updated Oracle needs to verify the child table’s FK constraints. In the absence of an index, this means a full table scan and a table lock. Normally foreign keys point to primary keys in the parent table, and you rarely need to change a primary key, so you’d think this should not happen often. But deleting rows from the parent table also has the potential of violating FK constraints. So operations that delete rows in the parent table can result in deadlocks in some cases.
Anyway, here’s the Oracle script to identify unindexed foreign keys:
select table_name, constraint_name,
       cname1 || nvl2(cname2,','||cname2,null) ||
       nvl2(cname3,','||cname3,null) || nvl2(cname4,','||cname4,null) ||
       nvl2(cname5,','||cname5,null) || nvl2(cname6,','||cname6,null) ||
       nvl2(cname7,','||cname7,null) || nvl2(cname8,','||cname8,null)
              columns
    from ( select b.table_name,
                  b.constraint_name,
                  max(decode( position, 1, column_name, null )) cname1,
                  max(decode( position, 2, column_name, null )) cname2,
                  max(decode( position, 3, column_name, null )) cname3,
                  max(decode( position, 4, column_name, null )) cname4,
                  max(decode( position, 5, column_name, null )) cname5,
                  max(decode( position, 6, column_name, null )) cname6,
                  max(decode( position, 7, column_name, null )) cname7,
                  max(decode( position, 8, column_name, null )) cname8,
                  count(*) col_cnt
             from (select substr(table_name,1,30) table_name,
                          substr(constraint_name,1,30) constraint_name,
                          substr(column_name,1,30) column_name,
                          position
                     from sys.user_cons_columns ) a,
                  sys.user_constraints b
            where a.constraint_name = b.constraint_name
              and b.constraint_type = 'R'
            group by b.table_name, b.constraint_name
         ) cons
   where col_cnt > ALL
           ( select count(*)
               from sys.user_ind_columns i
              where i.table_name = cons.table_name
                and i.column_name in (cname1, cname2, cname3, cname4,
                                      cname5, cname6, cname7, cname8 )
                and i.column_position <= cons.col_cnt
              group by i.index_name
           )

Related links

2 comments:

  1. this isn't accurate. I checked one of the foreign keys out and it did have a composite index

    ReplyDelete