Primarys Keys and their supporting indexes
29/01/2016 1 Comment
Sometimes things just happen which makes you realise that stuff you thought all experienced DBA’s knew about isn’t common knowledge. I suppose it’s a side effect of working with the same evolving product for over 25 years at dozens of clients, when the colleague you are talking to has a mere decade of experience at fewer companies (and therefore less diversity of challenge).
Today I noticed that a release was creating a table, then an index, and then adding a Primary Key based upon the index. Pretty normal, but the indexes were non-unique. Interesting. Is that valid? (yes) It is sensible? (under some circumstances, also yes). Is it wrong but nobody noticed until it got to me? Probably.
However, there’s more variables at play here than first meets the eye. The Primary Key is being enforced by a pre-existing non-unique index, rather than create its own index “dynamically” (which would be unique under most circumstances). So therefore we have a few questions to answer
- Is the index being used optimal?
- How do I know it’s using a pre-created index and the constraint did not create its own index?
- What happens if I drop the PK constraint?
Scenario 1: create table, create constraint
You get a nice neat fast index enforcing the constraint. If you drop the constraint, the index will also get removed. This may or may not be what you want. You may need to put a new index back on the table…
Scenario 2: create table, create unique index, create constraint
You get a nice neat fast index enforcing the constraint. If you drop the constraint, the index does not get removed. This may or may not be what you want. The index which was left behind might cause a problem if you miss the fact it’s still around…
Scenario 3: create table, create non-unique index (same as constraint columns), create constraint
The index enforcing the constraint is slightly less efficient enforcing the constraint as it has to optimize to an index range scan, not a unique scan. However, if you drop the constraint you still have a non-unique index. This may be a very useful ability for bulk data loads with questionable data, although the use-cases are limited and tend to be Warehouse-centric.
If you have a deferrable constraint, you must have a non-unique index to enforce the constraint as there may temporarily be duplicate keys in the table part-way through the transaction.
Scenario 4: create table, create non-unique composite index, create constraint which uses leading columns of that index but not all of the columns
The index may be somewhat less efficient enforcing the constraint by having less rows per block and possibly more levels, but this inefficiency may help overall system efficiency by having to maintain less indexes – maintaining 2 indexes instead of 1 is a 100% overhead in index maintenance. The composite index cannot be a unique index.
Some worked example showing the above scenarios (ran on Oracle 12.1.0.2.0 in a PDB) :
NEIL @ ORCL01 > -- Scenario 1 NEIL @ ORCL01 > drop table neil; Table dropped. NEIL @ ORCL01 > create table neil (col_id number, col_txt varchar2(10 char)); Table created. NEIL @ ORCL01 > alter table neil add constraint neil_pk primary key (col_id) ; Table altered. NEIL @ ORCL01 > select index_name,uniqueness from user_indexes where table_name = 'NEIL'; INDEX_NAME UNIQUENESS -------------------- -------------------- NEIL_PK UNIQUE NEIL @ ORCL01 > insert into neil values (1,'A'); 1 row created. NEIL @ ORCL01 > insert into neil values (1,'B'); insert into neil values (1,'B') * ERROR at line 1: ORA-00001: unique constraint (NEIL.NEIL_PK) violated NEIL @ ORCL01 > commit; Commit complete. NEIL @ ORCL01 > alter table neil drop constraint neil_pk; Table altered. NEIL @ ORCL01 > -- And the index is gone... NEIL @ ORCL01 > select index_name,uniqueness from user_indexes where table_name = 'NEIL'; no rows selected
NEIL @ ORCL01 > -- Scenario 2 NEIL @ ORCL01 > drop table neil; Table dropped. NEIL @ ORCL01 > create table neil (col_id number, col_txt varchar2(10 char)); Table created. NEIL @ ORCL01 > create unique index neil_i on neil(col_id); Index created. NEIL @ ORCL01 > alter table neil add constraint neil_pk primary key (col_id) ; Table altered. NEIL @ ORCL01 > select index_name,uniqueness from user_indexes where table_name = 'NEIL'; INDEX_NAME UNIQUENESS -------------------- -------------------- NEIL_I UNIQUE NEIL @ ORCL01 > insert into neil values (1,'A'); 1 row created. NEIL @ ORCL01 > insert into neil values (1,'B'); insert into neil values (1,'B') * ERROR at line 1: ORA-00001: unique constraint (NEIL.NEIL_PK) violated NEIL @ ORCL01 > commit; Commit complete. NEIL @ ORCL01 > alter table neil drop constraint neil_pk; Table altered. NEIL @ ORCL01 > -- And the index survives NEIL @ ORCL01 > select index_name,uniqueness from user_indexes where table_name = 'NEIL'; INDEX_NAME UNIQUENESS -------------------- -------------------- NEIL_I UNIQUE
NEIL @ ORCL01 > -- Scenario 3 NEIL @ ORCL01 > drop table neil; Table dropped. NEIL @ ORCL01 > create table neil (col_id number, col_txt varchar2(10 char)); Table created. NEIL @ ORCL01 > create index neil_i on neil(col_id); Index created. NEIL @ ORCL01 > alter table neil add constraint neil_pk primary key (col_id) using index neil_i; Table altered. NEIL @ ORCL01 > select index_name,uniqueness from user_indexes where table_name = 'NEIL'; INDEX_NAME UNIQUENESS -------------------- -------------------- NEIL_I NONUNIQUE NEIL @ ORCL01 > insert into neil values (1,'A'); 1 row created. NEIL @ ORCL01 > insert into neil values (1,'B'); insert into neil values (1,'B') * ERROR at line 1: ORA-00001: unique constraint (NEIL.NEIL_PK) violated NEIL @ ORCL01 > commit; Commit complete. NEIL @ ORCL01 > alter table neil drop constraint neil_pk; Table altered. NEIL @ ORCL01 > -- And the index survives NEIL @ ORCL01 > select index_name,uniqueness from user_indexes where table_name = 'NEIL'; INDEX_NAME UNIQUENESS -------------------- -------------------- NEIL_I NONUNIQUE
NEIL @ ORCL01 > -- Scenario 4 NEIL @ ORCL01 > drop table neil; Table dropped. NEIL @ ORCL01 > create table neil (col_id number, col_txt varchar2(10 char)); Table created. NEIL @ ORCL01 > create index neil_i_composite on neil(col_id,col_txt); Index created. NEIL @ ORCL01 > alter table neil add constraint neil_pk primary key (col_id) using index neil_i_composite; Table altered. NEIL @ ORCL01 > select index_name,uniqueness from user_indexes where table_name = 'NEIL'; INDEX_NAME UNIQUENESS -------------------- -------------------- NEIL_I_COMPOSITE NONUNIQUE NEIL @ ORCL01 > insert into neil values (1,'A'); 1 row created. NEIL @ ORCL01 > insert into neil values (1,'B'); insert into neil values (1,'B') * ERROR at line 1: ORA-00001: unique constraint (NEIL.NEIL_PK) violated NEIL @ ORCL01 > commit; Commit complete. NEIL @ ORCL01 > alter table neil drop constraint neil_pk; Table altered. NEIL @ ORCL01 > -- And the index survives NEIL @ ORCL01 > select index_name,uniqueness from user_indexes where table_name = 'NEIL'; INDEX_NAME UNIQUENESS -------------------- -------------------- NEIL_I_COMPOSITE NONUNIQUE
So how do I tell if, when I drop my constraint, I will also drop the index? This does not appear to be exposed in any DBA_ view of which I am aware, so I have had to rely upon this post from Jonathan Lewis to help me decipher the information. You need to see if certain bits are set in the column sys.ind$.property. Here’s my worked example to show the information we seek:
NEIL @ ORCL01 > drop table neil_cons_create; Table dropped. NEIL @ ORCL01 > create table neil_cons_create (col_id number, col_txt varchar2(10 char)); Table created. NEIL @ ORCL01 > alter table neil_cons_create add constraint neil_cons_create_pk primary key (col_id); Table altered. NEIL @ ORCL01 > drop table neil_pre_create; Table dropped. NEIL @ ORCL01 > create table neil_pre_create (col_id number, col_txt varchar2(10 char)); Table created. NEIL @ ORCL01 > create unique index neil_pre_create_i on neil_pre_create(col_id); Index created. NEIL @ ORCL01 > alter table neil_pre_create add constraint neil_pre_create_pk primary key (col_id); Table altered. NEIL @ ORCL01 > select index_name,uniqueness from user_indexes where table_name like 'NEIL%'; INDEX_NAME UNIQUENESS -------------------- -------------------- NEIL_CONS_CREATE_PK UNIQUE NEIL_PRE_CREATE_I UNIQUE select do.owner,do.object_name,do.object_id,ind.property, 'Keep Index' action from sys.ind$ ind ,dba_objects do where do.object_name like 'NEIL%' and do.object_id = ind.obj# and bitand(ind.property,4097) = 4097 union all select do.owner,do.object_name,do.object_id,ind.property, 'Drop Index' action from sys.ind$ ind ,dba_objects do where do.object_name like 'NEIL%' and do.object_id = ind.obj# and bitand(ind.property,4097) <> 4097 order by 1,2,3 / OWNER OBJECT_NAME OBJECT_ID PROPERTY ACTION ------------ ------------------- ---------- ---------- -------------------- NEIL NEIL_CONS_CREATE_PK 93814 4097 Keep Index NEIL NEIL_PRE_CREATE_I 93816 1 Drop Index
Pingback: Dropping constraint… what about the index? | Boneist's Oracle Blog