jump to navigation

IOT Secondary Indexes: Primary Key Considerations (Beauty And The Beast) April 11, 2012

Posted by Richard Foote in Block Dumps, Index Organized Tables, IOT, Oracle Indexes, Primary Key, Secondary Indexes.
trackback

As discussed previously, one of the nice features of an IOT Secondary Index is that it contains the mandatory Primary Key of the IOT, which is always maintained and can be used to access the necessary rows of the IOT regardless of  row movement within the IOT itself.

This can also be beneficial if only the PK columns of the table are required by the query (perhaps as part of a join) as a visit to the actual IOT table would be unnecessary.

However, one of the main disadvantages of an IOT Secondary Index is that it contains the PK of the IOT 🙂

Remember, one of the benefits of an IOT is that we don’t have to store columns twice as we would with a conventional Heap table, that being once within the table structure and again within the (often overloaded) PK  index. However, with an IOT Secondary index, we must store the PK columns again. In fact, we have to re-store the PK columns again for as many IOT Secondary indexes we have defined for the IOT.

So the overall additional overheads we’re talking about here becomes a product of two important factors. The actual overall size of the PK column(s) and the number of Secondary Indexes we have defined on the IOT. If the average size of the PK is large and/or we have a number of Secondary Indexes, then the overall overheads can be significant, reducing the benefits of the IOT.

If we look at the size of the IOT Secondary Index I created in my previous introductory post:

SQL> select leaf_blocks from dba_indexes where index_name = 'ALBUM_SALES_IOT_TOTAL_SALES_I';

LEAF_BLOCKS
-----------
       1728

If however we compare this with a secondary index associated with a conventional heap table containing identical data:

SQL> create table not_an_iot as select * from album_sales_IOT;

Table created.

SQL> create index not_an_iot_total_sales_i on not_an_iot(total_sales);

Index created.

SQL> select leaf_blocks from dba_indexes where index_name = 'NOT_AN_IOT_TOTAL_SALES_I';

LEAF_BLOCKS
-----------
       1171

We notice that the IOT Secondary index is significantly larger, 1728 leaf blocks vs. 1171.

If we compare block dumps of the same index entry from both Secondary Indexes:

row#0[8016] flag: K—–, lock: 0, len=20
col 0; len 2; (2):  c1 06
col 1; len 3; (3):  c2 15 16
col 2; len 2; (2):  c1 5f
tl: 8 fb: –H-FL– lb: 0x0  cc: 1
col  0: [ 4]  01 01 41 f1

Above is the IOT Secondary Index example, which is 20 bytes in length.

row#0[8024] flag: ——, lock: 0, len=12
col 0; len 2; (2):  c1 06
col 1; len 6; (6):  01 01 68 7a 00 b4

Above is the Heap Table Secondary Index example, which is only 12 bytes in length.

The 8 bytes required for the table header and physical “guess” overheads within the IOT Secondary Index are almost cancelled out by the 7 bytes of overhead required for the ROWID column within the Heap Table Secondary index. However, most of the difference in length (20 bytes vs. 12 bytes) can be attributed to the 7 bytes required to store the PK columns and their associated length bytes in this example.

Obviously, the larger the PK, the greater the associated overheads. Obviously, the greater the number of IOT Secondary indexes, again the greater the overall associated overheads.

If we create a secondary index on a column that forms part of the PK, Oracle can be a lit bit cleverer. Following, we create an index on the COUNTRY_ID column, which is the second column of our PK (album_id, country_id):

SQL> create index album_sales_iot_country_id_i on album_sales_iot(country_id);

Index created.

We notice that for this new index, Oracle has eliminated “redundant” PK columns from the secondary index, as there’s no need to store the entire PK again as the indexed column itself already forms part of the PK:

SQL> select index_name, iot_redundant_pkey_elim from dba_indexes where table_name = 'ALBUM_SALES_IOT';

INDEX_NAME                     IOT_REDUNDANT_PKEY_ELIM
------------------------------ ------------------------
ALBUM_SALES_IOT_PK             NO
ALBUM_SALES_IOT_TOTAL_SALES_I  NO
ALBUM_SALES_IOT_COUNTRY_ID_I   YES

A quick look at a block dump of this secondary index will confirm that the PK portion of the index entry only contains the PK columns that are not included in the indexed column list (i.e. just the ALBUM_ID column):

row#0[8020] flag: K—–, lock: 0, len=16
col 0; len 2; (2):  c1 02
col 1; len 2; (2):  c1 02
tl: 8 fb: –H-FL– lb: 0x0  cc: 1
col  0: [ 4]  01 01 38 e5
row#1[8004] flag: K—–, lock: 0, len=16
col 0; len 2; (2):  c1 02
col 1; len 2; (2):  c1 03
tl: 8 fb: –H-FL– lb: 0x0  cc: 1
col  0: [ 4]  01 01 38 e5
row#2[7988] flag: K—–, lock: 0, len=16
col 0; len 2; (2):  c1 02
col 1; len 2; (2):  c1 04
tl: 8 fb: –H-FL– lb: 0x0  cc: 1
col  0: [ 4]  01 01 38 e5

So we have 3 index entries listed here. In each one:

col 0 represents  the indexed column (COUNTRY_ID) which happens to be part of the PK

col 1 is the remaining PK column yet to be defined in the index entry (ALBUM_ID)

col 0 (with a length of 4) represents the physical “guess”.

So Oracle still has defined within the index entry the full PK to access the IOT as necessary if the “guess” proves to be wrong.

The key points to take from this post is to fully consider the consequences of a large PK on any defined secondary index on an IOT and to fully consider the suitability of having the table defined as an IOT if you require many secondary indexes to be defined on the table.

More on this “guess” component in my next post …

Comments»

1. Uwe Hesse - April 11, 2012

It would seem to me like a bad design idea if we would actually need many secondary indexes on an IOT for the very reasons you mentioned in this post.

Like

Richard Foote - May 7, 2012

Hi Uwe

Exactly. If you have lots of secondary indexes defined on your IOT, I would certainly question the design and implementation of IOT use. Then again, it all depends.

Like

2. Tony - April 12, 2012

This is a very timely post for me, just yesterday I started digging into some performance issues with some views based on several different IOTs. These tables are medium sized as far as column count goes, but do have a number of secondary indexes to support foreign key and other constraints.

The original query using the IOT tables had the following autotrace statistics.

Statistics
———————————————————-
29 recursive calls
0 db block gets
11258 consistent gets
0 physical reads
0 redo size
531944 bytes sent via SQL*Net to client
634 bytes received via SQL*Net from client
27 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6408 rows processed

By simply redefining the table as a standard heap the number of consistent gets went down significantly – about a 90% reduction.

Statistics
———————————————————-
165 recursive calls
0 db block gets
1281 consistent gets
623 physical reads
0 redo size
118559 bytes sent via SQL*Net to client
447 bytes received via SQL*Net from client
10 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
2012 rows processed

I then took it a step further and created partitioned versions of these tables and things got even better (as expected)

Statistics
———————————————————-
27 recursive calls
0 db block gets
700 consistent gets
111 physical reads
0 redo size
118612 bytes sent via SQL*Net to client
447 bytes received via SQL*Net from client
10 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
2012 rows processed

Not the same drastic changed, but a 45% improvement nonetheless. I think this speaks to the point of the overhead quite well. If we compare the IOT to the partitioned version we are talking about a 93%+ reduction in consistent gets.

Now if only the dbms_redefinition package moved the foreign keys and other constraints to the new table during the process.

Like

Tony - April 12, 2012

Doh! Copied the wrong query stats for the orignal. It should be

Statistics
———————————————————-
29 recursive calls
0 db block gets
4657 consistent gets
183 physical reads
0 redo size
117577 bytes sent via SQL*Net to client
447 bytes received via SQL*Net from client
10 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2012 rows processed

So, the original versus heap is 72% reduction and the original versus the partitioned tables is a 85% reduction. Still pretty good.

Like

Richard Foote - May 7, 2012

Hi Tony

Indeed, as I discussed in my next post, if the percentage of correct guesses is poor, the overall increase in LIO’s can be significant with IOT secondary indexes.

Like

3. Log Buffer #267, A Carnival of the Vanities for DBAs | The Pythian Blog - April 14, 2012

[…] Richard Foote says that one of the nice features of an IOT Secondary Index is that it contains the mandatory Primary Key of the IOT, which is always maintained and can be used to access the necessary rows of the IOT regardless of  row movement within the IOT itself. […]

Like

4. IOTs | Oracle Scratchpad - February 10, 2014

[…] Secondary Indexes – primary considerations […]

Like


Leave a comment