jump to navigation

Indexes On Small Tables Part I (One Of The Few) April 16, 2009

Posted by Richard Foote in Oracle Indexes, Small Indexes.
26 comments

A common question I get asked is when is a table too small to benefit from being indexed.

If a table only has a few blocks for example, which could all be potentially read via a single multiblock read operation, surely there’s no benefit in indexing such a table (except perhaps to police an associated PK constraint). It must take at least 2 Logical I/O (LIO) operations to read data from the table via an index, at least one LIO to read an index block and at least one LIO to read the associated table block referenced by the ROWID in the index. If a Full Table Scan (FTS) can be effectively performed via a single multiblock read operation, hence reading the entire table with just one LIO , surely an index will always be a more expensive option and so ultimately useless with such small tables.

Well not necessarily …

The first thing to point out is that generally speaking, a Full Table Scan is a relatively expensive operation. Tables can be big, really really big, consisting of potentially many many 1,000s of data blocks, potentially requiring many 1,000s of multiblock read operations to be performed. Therefore, generally speaking, if we’re going to perform a relatively expensive FTS, we’re not going to be too concerned if we use an extra I/O or two, as we potentially have to perform 1,000s of I/Os anyways. A shortcut here or there is not going to generally make much of a difference one way or the other.

Note also that with a FTS being this relatively expensive operation, we’re not likely to generally speaking want to perform 1,000s of such FTS operations every minute within our databases. Generally speaking, a FTS is a much less common event than an Index Range Scan operation and so we wouldn’t take advantage of any possible short cuts here or there very often.

However, generally speaking, an index scan is a relatively inexpensive operation, potentially consisting of just a few LIO operations. We may have an index that has a blevel of say 2 (height of 3) and we may typically only want to select a row or two. That would therefore consist of just 3 LIOs of read the index related blocks (the index root block, an index branch block and an index leaf block) plus an I/O or two to read a row or two from the table. It’s potentially just a handful of blocks, just a few little LIOs but if we could somehow save an I/O or two in the process, this could in fact make a huge difference to the relative costs of the Index Range Scan.

Note also that with an Index Range Scan being this relatively inexpensive operation, we’re quite like to generally speaking want to perform lots and lots of such Index operations each and every minute in our databases. Generally speaking, an Index Range scan is a very very common event and so any short cut here or there can be extremely useful and significant and be taken advantage of frequently within the database.

So a FTS has a tendency to be relatively expensive and is not performed anywhere near as as frequently as Index Range Scan operations which have a tendency to be relatively inexpensive. Generally speaking of course.

But Oracle takes this generalisation very much to heart in how it goes about processing these operations.

The next point to make is that if a table has just a few rows and say consists of  just the one data block below its High Water Mark (HWM), it doesn’t necessarily mean we only need just the one I/O operation to read the entire table. For example, how does Oracle know there’s just one block worth of data ? How does Oracle know where to actually physically locate this one block worth of data ? How does Oracle know that once its read this block, there aren’t any other data blocks of interest ?

The answer is that it can’t without referencing data dictionary objects and without accessing the table segment header where the extent map is located. Even for a tiny table with only a handful of rows that can reside in only the one table block, it therefore requires more than just the one consistent get operation to read data from the table via a FTS. However, as a FTS is usually a relatively expensive operation, these few little consistent reads here and there to determine the actual number of blocks in the table and the actual location of these blocks is generally going to be a relatively trivial overhead. Oracle though doesn’t differentiate between a small and a larger table when it comes to a FTS, so these extra few consistent reads can potentially be a significant overhead for FTS operations on smaller tables.

As an example, let’s create a little table and see what consistent gets are required to read it via a FTS …

Let’s begin by creating a small table that consists of just 100 little rows.

SQL> CREATE TABLE small AS SELECT rownum id, ‘BOWIE’ name FROM dual CONNECT BY LEVEL <= 100;

Table created.

SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=>’SMALL’, estimate_percent=> null, method_opt=> ‘FOR ALL COLUMNS SIZE 1′);

PL/SQL procedure successfully completed.

SQL> SELECT blocks from user_tables WHERE table_name=’SMALL';

    BLOCKS
----------
         1

Note that this table consists of just the one data block below the HWM. A table can’t really get much smaller that one block.

Let’s now select just one row from this table. Note we haven’t created an index at this point so Oracle has no choice but to read this one row via a FTS.

SQL> SELECT * FROM small WHERE id = 42;

        ID NAME
---------- -----
        42 BOWIE

 

Execution Plan
------------------------------------------
|Id  | Operation         | Name  | Rows  |
------------------------------------------
|  0 | SELECT STATEMENT  |       |     1 |
|* 1 |  TABLE ACCESS FULL| SMALL |     1 |
------------------------------------------
Statistics
--------------------------------------------
  0  recursive calls
  0  db block gets
  4  consistent gets
  0  physical reads
  0  redo size
465  bytes sent via SQL*Net to client
396  bytes received via SQL*Net from client
  2  SQL*Net roundtrips to/from client
  0  sorts (memory)
  0  sorts (disk)
  1  rows processed

 

Note to read just this one row from this one block table, we have actually performed 4 consistent gets operations. Not 1 consistent get, but 4 consistent gets …

Let’s look at the actual type of consistent gets, by running the following statement in another session before and after executing the above SELECT statement (note SID 134 refers to the session SID that ran the above SELECT statement) :

SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n
     WHERE s.statistic# =n.statistic# AND s.sid = 134 AND n.name LIKE ‘consistent%';

NAME                           VALUE
----------------------------- ------
consistent gets               275851
consistent gets - examination  70901

Note the above figures were the session consistent gets before the SELECT statement and the following consistent gets statistics are after the SELECT statement was executed. 

SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n
     WHERE s.statistic# =n.statistic# AND s.sid = 134 AND n.name LIKE ‘consistent%';

NAME                           VALUE
----------------------------- ------
consistent gets               275855 (+4)
consistent gets - examination  70901 (0)

Note that yes indeed, there were 4 consistent gets performed and that none of the consistent gets were the “cheaper” consistent gets examinations. Therefore, the 4 consistent gets used in performing the FTS of the one block table required 4 x 2 = 8 latches.

Now 4 consistent reads to perform a FTS isn’t too bad, even for this little table and 8 latches isn’t exactly a huge number.

However, as we’ll see next, an index on this tiny one block table can do so much better …

Follow

Get every new post delivered to your Inbox.

Join 1,883 other followers