Monterey and ODTUG Kaleidoscope 2009 Review June 30, 2009
Posted by Richard Foote in Kaleidoscope, Oracle ACE Director, Richard's Musings, Travel.9 comments
I’ve just recently returned from a really enjoyable trip to the USA to attend an Oracle ACE Director briefing at Redwood Shores and the ODTUG Kaleidoscope Conference in Monterey, CA.
During the week, I had the opportunity to finally meet some of the other Oracle ACE Directors such as Tim Hall, Mark Rittman, Lucus Jellema, Lonneke Dikmans, Mike van Alst, Husnu Sensoy, Debra Lilley and Harshad Oak to name but a few. I also had a great time hanging out with fellow Aussies Chris Muir and Marcel Kratochvil, sharing many a beer and bottle of red wine. A highlight was sitting outside one balmy evening, drinking one of Marcel’s expensive reds with Chris and Tim Hall, whist discussing the bizzare topic of body hair !! During the week, it was announced Chris had been awarded the Oracle ACE Director of the Year, a much deserved honour and Marcel got the opportunity to spread his dislike of having Foreign Keys in the database to anyone who would (and would not) listen.
I also got to meet the infamous Stanley as well. Life can be strange at times …
The ACE Director briefing was a very long day. I can’t say too much until tomorrow’s big Oracle Fusion Middleware 11g Global Launch …
How many Oracle ACE Directors do you recognise (and can you spot me)
The ODTUG Kaleidoscope Conference was excellent, a really well run event, held in the lovely surroundings of the Hyatt Hotel complex in beautiful Monterey, California. As usual with these types of events, the quality of the presentations varied considerably. Some of the highlights included an excellent presentation by Bryn Llewellyn on “Doing SQL from PL/SQL: Best and Worst Practices” and some really interesting presentations by Toons Koppelaars on the importance of “Fat Databases: A layered Approach” and on “Semantic Query Optimization”. Lowlights included a truly awful, error riddled presentation by Burleson on so-called Oracle 11g SQL Tuning Secrets. I was warned beforehand, I should have known better and listened …
Also had time to explore Monterey and check out some of its attractions. It’s a really scenic part of the world and it was great to escape the Canberra winter for a couple of weeks. The Monterey Bay Aquarium is a must visit, one of the most impressive I’ve ever visited and I’ve seen quite a few around the world. The Kelp Forest display with its school of Sardines was just superb as were the jelly fish tanks.
Fisherman’s Wharf was worth checking out with it’s old wooden buildings as was Cannery Row, made famous by John Steinbeck. However, probably the highlight for me was the Whale watching cruise where I along with Chris, Marcel and Debra Lilley were all lucky enough to see two Humpback Whales. What an incredible experience, although I almost (but not quite) nearly saw my breakfast again in the choppy conditions. The smell of the Humpback whale’s plume (something akin to a mixture of rotting broccoli and rotting fish) had me gagging again but I managed to just hang on. If you ever get the chance, do it, but pick a mild calm day if possible.
Finally, I also had the very great pleasure in meeting members of the hard working OTN team responsible for administrating the Oracle ACE program (Justin Kestelyn : follow link for photos, Todd Trichler, Vikki Lira and Lillian Buziak). Thanks guys for all your wonderful work and support in organising such a wonderful event for all the Oracle ACEes.
Back to the CBO and indexes in the next few days.
The CBO and Indexes: Introduction Continues … June 15, 2009
Posted by Richard Foote in CBO, Oracle Indexes.9 comments
OK, I previously briefly covered how the CBO calculates the basic cost of an index range scan. Yes, those cardinality/rows values in the execution plans are vitally important as they highlight whether or not the CBO has used the appropriate selectivity values in the index costing formula. And yes, the associated cost values are meaningful and potentially useful as they determine the actual costs associated with the execution plan in terms of the expected number of I/Os the CBO estimates will be required (when using the IO costing model and often the CPU costing model as well).
I’m just going to look at another example now using the same table setup as before, but this time running an SQL query that has 5 distinct values in an IN list predicate on our demo table (again, follow the link to see the query and formatted execution plan).
The first thing we notice in this example, is that Oracle has decided to use a FTS rather than use the index on the ID column. Considering we’re only after 5 values out of the possible 100 values, some may not see this as expected behaviour, especially considering the index has such a good Clustering Factor. Basically Oracle is deciding to access each and every block below the HWM of the table, retrieving all 100% of the rows in the table, only to ultimately discard 95% of them.
It certainly appears at first glance to be a more “costly” option than using the index to directly access just the 5% of rows we’re interested in …
The first thing to check is the estimated cardinality figures, to see if the CBO has miscalculated the expected number of rows it needs to retrieve. However, as the statistics have just been fully computed and that the ID column has perfectly even distributed values, we notice the cardinality figures are again spot on. The query returns 10,000 rows and indeed the rows estimate in the execution plan is exactly10,000 rows. The calculation is simply 0.01 (density of column) x 200,000 (rows) x 5 (values in select list) = 10,000.
Let’s now calculate the cost of using the index using our index costing formula, using the CEIL function this time
index blevel + ceil(index selectivity x leaf blocks) + ceil(table selectivity x clustering factor)
2 + 5 x ceil(0.01 x 602) + ceil(0.05 x 854) = 2 + 5 x 7 + 43 = 37 + 43 = 80.
So the cost of using an index range scan to retrieve 5% of the rows in our example comes to a total of 80.
If we look at the cost of the FTS in our explain plan in the above link, we notice the cost is just 65. 65 is less than 80, so the FTS wins.
So how did the CBO come to a cost of just 65 when it has to read all 659 blocks in the table ?
Well, the first missing piece of information is the value of the db_file_multiblock_read_count parameter because this governs how many blocks Oracle will attempt to read within a single logical multiblock I/O call. Remember, when performing a FTS, Oracle knows it has to read all the table related blocks below the HWM and so rather than reading one tiny little block at a time, it does so more efficiently by reading multiple blocks at a time. This is the fundamental advantage of the FTS over the index range scan which can only ever access the one block at a time.
SQL> show parameter db_file_multi
NAME TYPE VALUE ----------------------------- ------- ----- db_file_multiblock_read_count integer 16
So the db_file_multiblock_read_count is 16.
The next thing to note is that it’s very unlikely that Oracle will actually read the full 16 blocks at a time as there are a number of factors that prevents this from occurring. Extent boundaries is one classic example (a multiblock read can not span across extent boundaries) but the more common issue is a block within the table already being stored in the buffer cache. Rather than storing the same block at the same consistent point twice in memory, Oracle breaks up the multiblock read and only reads up to the block that is already cached in the buffer cache. Therefore, for Oracle to actually read the entire table using the full 16 block multiblock I/Os, it would mean there are no cached blocks from the table currently in the buffer cache, an unlikely event.
Therefore, Oracle doesn’t use the full 16 value when determining the number of expected multiblock I/Os, but a modified “fudge” value which equates to approximately 10.4. for a MBRC of 16. Again, Jonathan Lewis in his excellent “Cost-Based Oracle Fundamentals” book discusses all this is some detail.
Remember also that Oracle needs to access the segment header as part of a FTS as I explained is some detail in my “Indexes and Small Table” series. So that’s an additional single block I/O on top of the multiblock I/Os.
Therefore the cost of performing a FTS is:
segment header I/O + ceil(table blocks/fudged mbrc value) = 1 + ceil(659/10.4) = 1 + 64 = 65.
The 65 cost for the FTS does make sense when one understands a little how this value is derived by the CBO …
As the FTS can read big chunks of the table at a time whereas the index range scan can only read each necessary block one at a time, the FTS can indeed read the table and retrieve the required 5% of data in fewer LIOs and so has the lesser associated cost than the index.
Now there are a few issues with all of this. Firstly, is the db_file_multiblock_read_count actually a valid and correct setting as this directly impacts not only the actual size of the multiblock read operations but critically, the associated costs relating to FTS operations (and indeed Fast Full Index Scans as well) ?
Also, is it really correct and valid to assume the cost of a multiblock I/O to be the same and equal to the cost of a single block I/O ? Surely, the process of performing a single block I/O is likely to be “cheaper” than that of a multiblock I/O and yet the CBO treats both types of I/Os as having the same fundamental “cost”.
Also the CPU overheads of having to access each and every row in each and every block is likely going to be more significant than the CPU required to access just specific data from specific blocks when using an index.
Perhaps, the more “expensive” index range scan might actually be a better alternative than the FTS if these factors were taken into consideration ?
Now this may indeed be true, if these factors were correctly taken into consideration. However, this may also indeed be quite false and the FTS may really truly be the better and more efficient alternative and attempts to force the use of the index may be inappropriate and ultimately more expensive.
I’ll next discuss some really bad (although still very common) methods of making the CBO favour indexes, using generally inappropriate so-called “Silver Bullets” …
Radiohead “In-Rainbows” Disk 2 Available For Digital Download June 13, 2009
Posted by Richard Foote in Music, Radiohead.add a comment
For all the Radiohead fans out there, some exciting news.
They’ve just announced that their “In Rainbows” Disk 2, which was previously only available with the limited edition ”In Rainbows” Box-Set, is now available for digital download from their w.a.s.t.e site.
I wrote a piece on the Radiohead “In Rainbows” Box-Set a while ago when it was first released, where I briefly described each track. It’s a fantastic collection of songs, with “Last Flowers To The Hospital” which dates all the way back to “OK Computer” being worth the price of admission alone.
If you’re a Radiohead fan, or simply a lover of great rock music, I highly recommend you check it out.
Dunning-Kruger Effect (The Idiot) June 11, 2009
Posted by Richard Foote in Richard's Musings.8 comments
In an interesting blog piece by Martin Widlake regarding his annoyance at just how unhelpful some people can unfortunately be in forums, Jonathan Lewis has a really interesting comment regarding the Dunning-Kruger Effect.
I’ve never heard of it before, but it’s really all quite fascinating. In short, the Dunning-Kruger Effect is when someone keeps making mistakes or coming to wrong conclusions but their incompetence robs them of the ability to realise it. Some of the hypotheses of this effect include:
- Incompetent people tend to overestimate their own level of skill
- Incompetent people tend to fail to recognise skills in others
- Incompetent people tend to fail to recognise the extent of their own inadequacies
David Dunning and Justin Kruger who came up with this theory won an Ig Nobel Prize for their report “Unskilled and Unaware Of It: How Difficulties In Recognising Ones Own Incompetence Lead To Inflated Self-Assessments“. The Ig Nobel Prize is given each year for 10 achievements that first make people laugh and then makes them think.
Boy, did I had a chuckle when some people instantly sprang to mind as I read about it all
The CBO and Indexes: An Introduction (Absolute Beginners) June 9, 2009
Posted by Richard Foote in CBO, Oracle Indexes.11 comments
One of the more common questions I get asked and one of the most common questions asked in the various Oracle related forums is the general question of why doesn’t the CBO choose to use a particular index. There are various reasons why an index might be ignored but generally and rather simplistically (for now), the answer is often simply because the CBO considers the cost of using the index to be more expensive than other alternatives.
As one would expect with the CBO, the cost is a rather important consideration and yet many don’t understand what the costs associated with an execution plan actually means and represents. Some people think the costs associated with an execution plan are basically meaningless and are only used by the CBO in “mysterious ways”. Some people even go so far as to suggest that the CBO sucks and recommend all sorts of inappropriate ways to force Oracle to use an index.
It all comes back to simply not understanding CBO fundamentals and how Oracle costs the basic use of an index.
In reality of course, the CBO doesn’t “suck” with 10g, in fact it’s actually extremely clever and resourceful when it comes to determining appropriate, efficient execution plans. If the CBO is armed with ”accurate enough” statistics, it will generally do a remarkably good job. The costs and the associated cardinality of the various steps within an execution plan provide valuable information on why the CBO has made its decisions.
So I thought I might discuss the CBO a little and start with a very basic introduction into how the CBO costs an index to get this message across.
The basic formula for costing an index based range scan is:
basic index range scan cost = index blevel + ceil(index selectivity x leaf blocks) + ceil(table selectivity x clustering factor)
Note there are various slight variations and interpretations of this formula from database version to database version and for differing specific scenarios, which can vary the resultant costs marginally. All the following examples are from a windows based 10.2.0.3 database.
So from an index perspective, the index blevel, the number of leaf blocks and the clustering factor are all statistics that directly influence the cost of using the index. However, just as important are the associated selectivities of accessing both the index and the table. These statistics are often based on the associated column statistics (but not always) and if Oracle gets these selectivities wrong, then the CBO will generate wrong costings and all bets are off regarding the appropriateness of the resultant execution plan.
OK, let’s create a nice, simple little scenario which will be the basis of future demos.
SQL> CREATE TABLE bowie_stuff AS SELECT (mod(rownum,100)+1)*10 id, ‘Ziggy Stardust’ name FROM dual CONNECT BY LEVEL <= 100000;
Table created.
SQL> CREATE INDEX bowie_stuff_i ON bowie_stuff(id);
Index created.
We’ll use the BOWIE_STUFF table in future demos, but for now I’m after a relatively large index with an excellent clustering factor. So I’ll create a second table, ordered on the ID column and double it’s size …
SQL> CREATE TABLE bowie_stuff2 AS SELECT * FROM bowie_stuff ORDER BY id;
Table created.
SQL> INSERT INTO bowie_stuff2 SELECT * FROM bowie_stuff2;
100000 rows created.
SQL> commit;
Commit complete.
SQL> CREATE INDEX bowie_stuff2_i ON bowie_stuff2(id);
Index created.
SQL> exec dbms_stats.gather_table_stats(ownname=> null, tabname=> ‘BOWIE_STUFF2′, cascade=> true, estimate_percent=> null, method_opt=> ‘FOR ALL COLUMNS SIZE 1′);
PL/SQL procedure successfully completed.
If we now look at some of the key statistics, we note the following:
SQL> SELECT table_name, blocks, num_rows FROM user_tables WHERE table_name = ‘BOWIE_STUFF2′;
TABLE_NAME BLOCKS NUM_ROWS ------------ ------ -------- BOWIE_STUFF2 659 200000
The table has 200,000 rows and consists of 659 data blocks.
SQL> SELECT column_name, num_distinct, density, num_nulls FROM user_tab_col_statistics WHERE table_name = ‘BOWIE_STUFF2′ and column_name = ‘ID’;
COLUMN_NAME NUM_DISTINCT DENSITY NUM_NULLS ----------- ------------ ------- --------- ID 100 .01 0
The indexed ID column has 100 distinct values, which are perfectly evenly distributed. The density of the ID is an “accurate” 0.01 with a selectivity of 1%, so a selection of one distinct value will return 200,000 x 0.01 = 2,000 rows. Nice easy numbers …
SQL> SELECT index_name, blevel, leaf_blocks, num_rows, distinct_keys, clustering_factor FROM user_indexes WHERE index_name = ‘BOWIE_STUFF2_I’;
INDEX_NAME BLEVEL LEAF_BLOCKS -------------- ------- ----------- BOWIE_STUFF2_I 2 605 NUM_ROWS DISTINCT_KEYS CLUSTERING_FACTOR -------- ------------- ----------------- 200000 100 854
The index has a blevel of 2, it has 605 leaf blocks and a rather good clustering factor of just 854 (note the rows in the table were basically inserted in ID order).
OK, we now have all the information we need.
Note to start with, I’ll only use the “older” IO costing model by setting the following parameter:
SQL> alter session set “_optimizer_cost_model” = io;
Session altered.
As we’ll see in future posts, the use of the CPU costing model actually has minimal effect on many index related access costings but we’ll use the IO costing model as a starting point.
This first equality SQL demo shows the costings in relation to a simple, single value equality predicate (I’ve linked to a PDF to preserve formatting).
OK, 2 important pieces of information to note. Firstly, the CBO has got the expected cardinality (2000 rows) spot on.
As the stats are 100% accurate and the values are perfectly evenly distributed, this is to be expected. If Oracle gets the cardinality correct, we can be reasonably confident that the CBO to have made a reasonable decision here.
As previously mentioned, there are 100 distinct values of which we want one of those those values. Oracle has calculated the selectivity of the query to be 1/100 = 1% or a value of 0.01 (as defined in the density column statistic). The expected number of rows is therefore 200,000 x 0.01 = 2000. Like I said, spot on.
Secondly, the CBO has calculated the cost of the index range scan to be 9 and the overall cost of the query to be 18. How has the CBO derived these costings ? Well as it’s a simple, single column index and associated query predicate, we can plug in the 0.01 selectivity into the above formula as follows:
index blevel + ceil(index selectivity x leaf blocks) + ceil(table selectivity x clustering factor)
2 + ceil(0.01 x 602) + ceil(0.01 x 854) = 2 + 7 + 9 = 9 + 9 = 18
So we can see that the costs associated with accessing just the index:
blevel + ceil(index selectivity x leaf blocks) = 2 + ceil(0.01 x 602) = 2 + 7 = 9
indeed comes to a total of 9
and the overall cost of the execution plan indeed does come to 18.
The CBO costings all do actually make sense and add up as expected.
In this second IN list demo, we’re just going to expand on things a touch by selecting 3 distinct values in a IN list. However, the actual principles and cost calculations are basically the same.
This time we’re after 3 possible values as listed in the IN clause. So that’s 3 out of the 100 possible values, 3/100 = 3% or 0.03. The required cardinality is therefore 200,000 rows x 0.03 = 6000 which again Oracle has got spot on. Usually a good sign that the CBO has made a reasonable decision.
This time the costs have come to 23 for the index range scan part of the execution plan and 49 overall. Again using our basic formula:
index blevel + ceil(index selectivity x leaf blocks) + ceil(table selectivity x clustering factor)
2 + 3 x ceil(0.01 x 602) + ceil(0.03 x 852) = 2 + 3×7 + 26 = 2 + 21 + 26 = 23 + 26 = 49.
So the cost associated with the index is indeed 23 and the overall cost of the query is indeed 49. Again, all these CBO costings make sense. These costings aren’t meaningless, internal numbers but values that give us a useful insight into how and why the CBO has come to a specific cost and so to a specific execution plan.
Note BTW, one of those subtle differences in how the formula is implemented with 10g (from say 9i) is the fact the selectivity is calculated for the index by performing the CEIL function for one value first and then multiplied by the overall number of expected values in the IN list:
2 + 3 x ceil(0.01 x 602) = 23
rather than determining and using the total index selectivity within the CEIL function:
2 + ceil(0.03 x 602) = 21
The net result being such costs are going to be just that little bit higher in 10g than they would have been in 9i.
So indeed, these things can all change a little from release to release but the basic principle is still the same. Feed the CBO acurate enough statistics and it’ll likely do the right thing. If it doesn’t, understanding the CBO and how it costs various execution plans will help to determine what the issues might be. Huge clues are provided by the costings and cardinality estimates in the associated query execution plans.
I’ll expand on all this is future posts but for more information on this subject, I highly recommend the writings of Wolfgang Breitling (and his excellent paper “A Look Under The Hood Of The CBO: The 10053 Event” in particular) and the writings of Jonathan Lewis (and his excellent book “Cost-Based Oracle Fundamentals” in particular).
Like I said, this is only meant as an introduction, more to come …
Indexes And Small tables Part VII (Cluster One) May 27, 2009
Posted by Richard Foote in Index Organized Tables, Oracle Indexes, Small Indexes.14 comments
OK, almost getting to the end here
As discussed previously, despite popular opinion, an index can be just that little bit more efficient than a FTS when accessing very small tables, even if all rows in the table exist in the one table block. And a small efficiency multiplied by a large number can potentially add up and make a noticeable difference.
As we’ve seen, a unique index on such a small table accessing a specific row of interest need only perform one consistent read on the index block and one consistent read on the table block for a grand total of 2 consistent reads, with both consistent gets being the cheaper examinations variety. Not bad, not too bad at all and somewhat cheaper than an equivalent FTS.
However, as I hinted and as many of you have already commented, we can go one step further still in reducing the overheads of such queries on small tables by potentially storing all the columns in the one, non-heap table structure.
One option is to create an Index Organized Table (IOT), storing all columns within a single index structure and thereby eliminating the need to visit a table segment at all.
Following on from the previous demo, let’s recreate the table as an IOT and populate it with the same data:
SQL> drop table small;
Table dropped.
SQL> create table small (id number primary key, name varchar2(10)) organization index;
Table created.
SQL> insert into small select rownum, ‘BOWIE’ from dual connect by level <=100;
100 rows created.
SQL> commit;
Commit complete.
SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=>’SMALL’, method_opt=>’FOR ALL COLUMNS SIZE 1′);
PL/SQL procedure successfully completed.
If we now run our query again:
SQL> select * from small where id = 42;
ID NAME ---------- ---------- 42 BOWIE Execution Plan ------------------------------------------ |Id|Operation |Name | ------------------------------------------ | 0|SELECT STATEMENT | | |*1| INDEX UNIQUE SCAN|SYS_IOT_TOP_68376 | ------------------------------------------ Statistics ------------------------------------------- 0 recursive calls 0 db block gets 1 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
We notice that we have now reduced the number of consistent gets down to just one.
Not only is it just one consistent get but if we look at the type of consistent get by running the following query in another session before/after our SELECT above:
SQL> select name, value from v$sesstat s, v$statname n where s.statistic#=n.statistic# and sid = 141 and name like ‘consistent gets%’;
NAME VALUE ----------------------------- ------ consistent gets 32842 consistent gets - examination 6694
SQL> select name, value from v$sesstat s, v$statname n where s.statistic#=n.statistic# and sid = 141 and name like ‘consistent gets%’;
NAME VALUE ----------------------------- ------ consistent gets 32843 (+1) consistent gets - examination 6695 (+1)
We also notice that it’s the cheaper, one latch consistent gets examination.
So we’ve now reduced our overheads down to just one consistent get and just the one latch get as well. It doesn’t really get much cheaper than that.
IOT are one of those under used options in Oracle that really should be considered used a lot more than they are. Yes they can be problematic when used inappropriately (especially if you need to create several secondary indexes) but for scenarios such as this they can be very useful.
I plan to discuss the advantages and disadvantages of IOT in future posts.
Another posssible option to improve things in our little demo is to create a Hash Cluster (as commented by Piet):
SQL> create cluster small_cluster (id number) size 100 single table hashkeys 200;
Cluster created.
SQL> create table small_tab (id number, name varchar2(100)) cluster small_cluster(id);
Table created.
SQL> insert into small_tab select rownum, ‘BOWIE’ from dual connect by level <=100;
100 rows created.
SQL> commit;
Commit complete.
SQL> exec dbms_stats.gather_table_stats(ownname=>null, tabname=>’SMALL_TAB’, estimate_percent=>null, method_opt=>’FOR ALL COLUMNS SIZE 1′);
PL/SQL procedure successfully completed.
SQL> select * from small_tab where id = 42;
ID NAME ---- ---------- 42 BOWIE Execution Plan --------------------------------------- | Id | Operation | Name | --------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | TABLE ACCESS HASH| SMALL_TAB | --------------------------------------- Statistics ------------------------------------------- 0 recursive calls 0 db block gets 1 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 again we have got our consistent gets down to just one as Oracle can simply determine the correct hash key based on the specified ID value and go directly to the table block containing the row of interest. Note however, this will not be a consistent get – examination, but the more expensive 2 latch and pin the block variety.
However, if you now create a unique index on the ID column:
SQL> create unique index small_tab_i on small_tab(id);
Index created.
And re-run the query:
SQL> select * from small_tab where id = 42;
ID NAME ---- ---------- 42 BOWIE Execution Plan --------------------------------------- | Id | Operation | Name | --------------------------------------- | 0 | SELECT STATEMENT | | |* 1 | TABLE ACCESS HASH| SMALL_TAB | --------------------------------------- Statistics ------------------------------------------- 0 recursive calls 0 db block gets 1 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
Oracle will still use the hash to determine the location of the row of interest but because it now knows it will only retrieve the one row, will do so with only a consistent get – examination. Back to one consistent get and only one latch get as well.
Again, clusters and all their various types and forms will be discussed in future blog entries.
Perhaps our good old familar heap table might not always be the best and most efficient option when creating these small tables (or even larger tables for that matter).
But for now the key message from this series is that any table, no matter how small can potentially benefit from being indexed. There really is no such thing as a table that’s too small to benefit from an index.Yes the difference might be small and of no real consequence but then again for larger database environments the overall savings might all add up and surprise. Note that the associated costs of having such indexes are also likely to be relatively small so perhaps it might just be worthwhile indexing those small tables after all
Two Excellent Index Related Blog Posts May 25, 2009
Posted by Richard Foote in Oracle Indexes.add a comment
Before I sit down and write something new, thought I might quickly mention two excellent recent index related blog posts,written by two very clever chaps.
The first is by Jonathan Lewis who discusses in a post called Index Size how one might determine which indexes to perhaps consider for an index rebuild by using the dbms_space.create index_cost procedure. By comparing the difference between the current size of the index and the potential size of a newly created index as calculated by the procedure, one can check to see if the difference in size might make a rebuild a worthwhile proposition. Jonathan then discusses a number of deficiencies and little traps with this approach.
The second is by Alberto Dell’Era who discusses in a post called Order of keys inside index blocks exactly how Oracle orders and stores the index keys within an index block. I’ve exchanged a number of emails with Alberto on this subject and he does an excellent job of detailing exactly how Oracle orders the index row directories and the advantages of Oracle doing so.
Both are highly recommended and well worth a read if you’re interested in learning more on Oracle indexes.
Indexes And Small Tables Part VI (Loaded) May 19, 2009
Posted by Richard Foote in Constraints, Oracle Indexes, Small Indexes, Unique Indexes.7 comments
Thanks to comments by PdV, I need yet another Part before I can look at completing this series
OK, we’ve reached the stage in Part V of accessing this small, one block table with a Unique Index. This has reduced the number of consistent gets to 2, with both consistent get operations being the “pinless”, one latch consistent get examinations.
We basically need one consistent get to read the index and the other consistent get to read the row from the table block.
Not bad.
However, if we could somehow just store all the columns of interest within the index structure, we could potentially do even better because then we wouldn’t need to visit the table segment at all. A single consistent get from the index and bingo, we can retrieve all the required data directly from the index.
Overloading an index in this way is actually quite a common tuning trick. Simply add additional columns within the select list so that all the required columns can be found within the index, thereby elliminating the need to access the table at all and so potentially improve performance.
However, when it comes to overloading a unique index designed to specifically police a PK or Unique constraint we have a slight problem. Oracle will not allow a unique constraint to be policed by a unique index that does not have the same column list. It’s not a problem for a non-unique index (providing the leading columns match the constraint columns), but it’s an issue for a unique index.
Therefore, in our little example, we can’t simply create a single concatenated unique index on both the ID and NAME columns and use it to police a unique constraint on just the ID column. We must either use a unique index on the ID column or use a non-unique index on both the ID and NAME columns. If we want to create a unique index on both the ID and NAME columns, we would need to create an additional index on the ID column to police the PK on the ID column or change our business rules to make both the ID and NAME the PK (which is not likely something we would want to do). Note also by doing creating 2 unique indexes, we would effectively be storing the ID column in three separate places, within the table, within the ID index and also within the ID and NAME index. Again, not something we’re likely going to want to do.
To illustrate the point, drop any existing indexes on the SMALL table. If we attempt to create a unique index on both the ID and NAME columns while making the ID column only the PK, Oracle is going to complain:
SQL> alter table small add primary key(id) using index (create unique index small_uk_i on small(id, name));
alter table small add primary key(id) using index (create unique index small_uk_i on small(id, name))
*
ERROR at line 1:
ORA-14196: Specified index cannot be used to enforce the constraint.try and cr
If we create a unique index first on both the ID and NAME columns:
SQL> create unique index small_uk_i on small(id, name);
Index created.
And then hope Oracle will simply use this index to police a PK constraint on just the ID column, we’ll be sadly disappointed as Oracle will actually created another unique index on the ID column behind the scenes:
SQL> alter table small add primary key(id);
Table altered.
SQL> select c.index_name, c.column_name from user_indexes i, user_ind_columns cwhere i.index_name = c.index_name and i.table_name = c.table_name and i.table_name = ‘SMALL’;
INDEX_NAME COLUMN_NAME ------------ ------------ SMALL_UK_I ID SMALL_UK_I NAME SYS_C009759 ID
The CBO will of course potentially look at using our SMALL_UK_I concatenated unique index to perform the select statement of our demo, but the efficiency results are not quite what we’re after:
SQL> select id, name from small where id = 42;
ID NAME ---------- ----- 42 BOWIE ----------------------------------- |Id| Operation | Name | ----------------------------------- | 0| SELECT STATEMENT | | |*1| INDEX RANGE SCAN| SMALL_UK_I| ----------------------------------- Statistics ------------------------------------------- 0 recursive calls 0 db block gets 2 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
We first note Oracle is indeed using the concatenated, unique SMALL_UK_I index as it can retrieve all the necessary data from the query directly from the index.
However, also note the CBO is performing an index range scan, not a unique scan as only a portion of the index (the ID) is specified in the WHERE clause. Oracle doesn’t pick up the fact the select operation is actually unique as not all columns within the SMALL_UK_I unique index used by the CBO are specified in the WHERE clause. This despite the fact the ID is actually the defined PK of the table.
Therefore, Oracle is still performing 2 consistent get operations as there may be more rows to retrieve after performing the first fetch within the SQL*PLUS session. Also, if we examined the types of consistent reads being performed, we would note that neither of them are consistent get – examinations.
So we’re really not that far ahead of just using the unique index on the ID column as we did in Part V of this series. We still require 2 consistent gets (although neither of them are now examinations) and we’re having to store the ID in three separate locations for our trouble, rather than two.
Wouldn’t it be nice if we could have a PK index on just the ID column, but somehow add the NAME column (or any other columns of interest) to the index structure so that we only need to visit the index structure, thereby storing the ID in only the one index. Then we could potentially access the data with just one consistent get and with it being a unique index, it would be a consistent get examination requiring only the one latch access.
Hell, wouldn’t it be nice if we didn’t even bother with the table segment at all as all queries of interest would never actually need to access and use the table segment anyways, thereby storing the PK in possibly just the one location.
Such a solution has of course been possible for a long time …
Indexes And Small Tables Part V (It’s No Game) May 13, 2009
Posted by Richard Foote in Index Internals, Oracle Indexes, Small Indexes, Unique Indexes.10 comments
So far in our little example, we’ve looked at how accessing a row of a one block table via a FTS required 4 consistent gets while accessing this same table via a Non-unique index reduced the consistent gets down to 3.
Time to take the next step and improve the efficiency yet further of accessing this small one block table.
We’re now going to replace the Non-unique index with a Unique Index instead. We can obviously do this because the values on the indexed ID column are indeed unique.
Now it’s always a good idea of course to document these table business rules (such as a column being unique) inside the database, however it’s somewhat alarming just how many application just don’t this. I’ve also previously discussed how a PK or Unique constraint can actually be policed via a Non-Unique index so there are many reasons why a small table might not have an associated Unique index.
Not least of course the incorrect perception that an index is not going to be much use on a small table anyways …
So let’s now replace the Non-Unique index with a Unique index instead:
SQL> drop index small_id_i;
Index dropped.
SQL> alter table small add primary key (id) using index (create unique index small_id_i on small(id));
Table altered.
OK, so now we have our Unique index in place. Let’s now run the same query again to see how our consistent gets related statistics might change:
SQL> select * from small where id = 42;
ID NAME ---------- ----- 42 BOWIE -------------------------------------------- |Id |Operation |Name | -------------------------------------------- | 1|TABLE ACCESS BY INDEX ROWID|SMALL | |* 2| INDEX UNIQUE SCAN |SMALL_ID_I| -------------------------------------------- Statistics -------------------------------------------- 0 recursive calls 0 db block gets 2 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
We notice our first significant change. The number of consistent gets has reduced further down to just 2.
Why ?
Because with a Unique index, there can only be a maximum of 1 row returned. It’s simply not possible to return 2 or more rows.
Therefore, when selecting this one row, Oracle doesn’t have to perform the second fetch operation to confirm there are indeed no more rows to return. The first fetch will either return the one row of interest or none at all when resolving an equality predicate. That’s it, there are no other possibilities. We return the row of interest by simply accessing the index block (1 consistent get) followed by the table block (the second consistent get).
As we have previously, if we look at the actual consistent gets statistics of interest by running the following query in another session before/after the select statement:
SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.statistic# =n.statistic# AND s.sid = 141 AND n.name LIKE ‘consistent%’;
NAME VALUE ------------------------------ ----- consistent gets 31236 consistent gets - examination 5084
And again afterwards ..
SQL> SELECT n.name, s.value FROM v$sesstat s, v$statname n WHERE s.statistic# =n.statistic# AND s.sid = 141 AND n.name LIKE ‘consistent%’;
NAME VALUE ------------------------------ ----- consistent gets 31238 (+2) consistent gets - examination 5086 (+2)
We notice we have indeed only performed the 2 consistent gets. But we also notice another significant difference, that being both consistent gets are now the “cheaper” consistent gets – examination.
This means that the latches required to now perform this select statement via the Unique index is just 2, down from 6 for the Non-unique index and 8 from the FTS.
Generally during a consistent get, Oracle needs to grab the cache buffers chain latch so it can pin the specific block in memory and then grab the latch again so that it can subsequently unpin the block once it’s finished processing the block. Each of these accesses to the latch and the subsequently pin/unpinning of the block requires CPU and is a possible source of contention within the database.
For some operations that only require a very very quick “read and get out of there” type operation and/or on blocks that are unlikely to change within a given point of time, Oracle uses a cheaper consistent get operation which doesn’t actually require the block to be pinned. There’s no point in pinning the block as it’s only going to be read and accessed for a short time (shorter than might otherwise be required when processing a block in memory) and the block is unlikely to change anyways.
So for these operations, Oracle uses a cheaper consistent get called a consistent gets – examination. These consistent gets examinations only need grab the cache buffers chain latch before quickly reading the block and releasing the latch once the read operation is complete. Therefore it only needs to grab and release the cache buffer chains latch the once without having to pin/unpin the block, which means less CPU and less latch contention overall.
Now this isn’t particularly well documented. Often discussions mention reads of undo blocks as being candidates for consistent gets examinations as these reads are likely to be both relatively quick and a specific undo block is unlikely to change as only one transaction can actually update an undo block at a given time.
Getting back to indexes, reads of index root blocks are another candidate mentioned as again a read of an index root block is going to be very quick and an index root block is unlikely to change at a given point of time.
However, what is not well documented at all is the fact that any block accessed during an index Unique Scan is accessed via a consistent get – examination, including the consistent get associated with reading the table block as well. This is because again, any such read operation is going to be relatively quick as the most that ever needs to be read is the one index related entry and the one table row.
The net result is that now accessing a row from a small table via a Unique index requires only 2 latch accesses vs. the initial FTS example which required 8 latch gets as none of the FTS consistent gets are examinations.
Now you might say that these are all very small numbers, that 4 consistent reads isn’t that much, that 8 latches isn’t really that huge a number and reducing 8 latches down to 2 latches isn’t really going to be that noticeable. Yes it is effectively a 75% reduction but it’s a 75% reduction of not very much.
And if you’re discussing a single read of a single small lookup table you would likely be right.
But what if the small table is accessed frequently by the application, perhaps many 1,000s of times per minute. What if you have many such small tables, often used in small join operations by your OLTP applications. What if you have large numbers of users in a large application with many many such small table accesses. This effectively 75% saving can potentially become very significant, both in terms of the reduction in CPU load and also in the reduction of latch contention, which in turn can further reduce CPU loads.
A small improvement multiplied by a large amount can indeed make a difference …
However, I have one more step to go yet in further improving the efficiency of these small table lookups via an index.
One which can reduce the overall overheads by yet another 50% …
Indexes And Small Tables Part IV (Treefingers) May 5, 2009
Posted by Richard Foote in Index Block Splits, Index Internals, Oracle Indexes, Root Index Block, Small Indexes.13 comments
As I asked in my previous post, the key question when comparing the associated costs of accessing a small table via a Full Table Scan (FTS) vs. an index scan is why does Oracle visit the segment header during a FTS but not during an index scan ?
The answer all comes down to understanding why Oracle must visit the table segment header during a FTS and how Oracle can avoid visiting the index segment header during an index scan.
Oracle must visit the table segment header during a FTS because it contains vital information necessary to perform the FTS, namely the extent map and the High Water Mark (HWM) associated with the table. Even with a table that only contains 1 data block worth of rows as in the SMALL table in my examples, Oracle has no way of automatically determining there’s actually only just the one block worth of data. It has to somehow look that up and determine exactly what table blocks Oracle needs to access during the FTS operation and the table segment header contains this necessary meta data. During “most” FTS operations, which are generally speaking larger, “expensive” operations, these accesses to the table segment header constitute a relatively small overhead. However, for FTS operations on “small” tables, accessing the table segment header can actually be a significant proportion of the overall associated costs.
During an index scan operation, there’s nothing of interest within the index segment header. The critical index block, the index block by which all index scans must start is the root block of the index (except Fast Full Index Scans which are basically the FTS equivalent for indexes). There’s no need to access the index segment header because it’s the root block that actually contains all the necessary information by which to start the index scan operation. The root blocks contains the pointers to subsequent index blocks (be it a branch or leaf blocks) that Oracle needs to follow in order to find the index entry of interest. The key to starting an index scan therefore is in determining the location of the index root block.
But how can Oracle determine the location of the index root block ?
Well Oracle implements a little “trick”, a golden rule with regard to indexes that doesn’t change regardless of the Oracle version, regardless of the O/S version, regardless of the type of tablespace or tablespace option of the index and regardless of how the index is created or grows and block splits over time.
The index root block is always, always, always the block immediately after the index segment header.
Always.
Therefore, when the Oracle code issues the associated function calls to perform an index scan, the first index block that Oracle assesses is the index segment header plus an offset of 1. Whereas a FTS accesses the table segment header, an index scan accesses the index segment block id plus 1.
With a tiny index that only has a level of 0 (or height of 1), note there is not “root” block as such as all the index entries can fit within one index leaf block. However, this block, this one and only leaf block within the index structure is also always the block immediately after the index segment header.
Always.
When we add more index entries into this one and only leaf block, we’ll eventually reach a point when it’s full and Oracle must perform an index block split operation. Oracle will then allocate 2 new blocks to the index. Assuming a 50-50 block split, one of these new blocks is assigned the lower 1/2 of all the current index entry values and the other new block is assigned the other upper 1/2 of the current index entry values. The original index leaf block content is then cleaned out and reassigned with just relative block address pointers and value boundaries associated with the 2 new leaf blocks.
The original index leaf block has been “reborn” as the root block of the index.
It’s quite easy to demonstrate how the original index block in a level 0 index or the root block of an index never changes and is always the block that follows the index segment header.
First, just create a little table and associated index:
SQL> CREATE TABLE same_root (id NUMBER, name VARCHAR2(30));
Table created.
SQL> INSERT INTO same_root VALUES (1, ‘The Thin White Duke’);
1 row created.
SQL> COMMIT;
Commit complete.
SQL> CREATE INDEX same_root_i ON same_root(name);
Index created.
If we dump the block immediately following the index segment header, we can confirm it’s our index block of interest, containing our one and only index entry:
SQL> select header_file, header_block from dba_segments where segment_name=’SAME_ROOT_I’;
HEADER_FILE HEADER_BLOCK ----------- ------------ 5 107441
SQL> alter system dump datafile 5 block 107442;
System altered.
Following is an extract of the index block dump:
Leaf block dump
===============
header address 98959964=0×5e6025c
kdxcolev 0
KDXCOLEV Flags = – - -
kdxcolok 0
kdxcoopc 0×80: opcode=0: iot flags=— is converted=Y
kdxconco 2
kdxcosdc 0
kdxconro 1
kdxcofbo 38=0×26
kdxcofeo 8007=0×1f47
kdxcoavs 7969
kdxlespl 0
kdxlende 0
kdxlenxt 0=0×0
kdxleprv 0=0×0
kdxledsz 0
kdxlebksz 8036
row#0[8007] flag: ——, lock: 0, len=29
col 0; len 19; (19): 54 68 65 20 54 68 69 6e 20 57 68 69 74 65 20 44 75 6b 65
col 1; len 6; (6): 01 41 a3 aa 00 00
—– end of leaf block dump —–
Note that it is indeed our one and only index leaf block.
If we now take a treedump of the index:
SQL> SELECT object_id FROM dba_objects where object_name = ‘SAME_ROOT_I’;
OBJECT_ID ---------- 67721
SQL> ALTER SESSION SET EVENTS ‘immediate trace name treedump level 67721′;
Session altered.
Following is the treedump output:
—– begin tree dump
leaf: 0×141a3b2 21078962(0: nrow: 1 rrow: 1)
—– end tree dump
We note that the relative block address of our one and only index leaf block is 21078962.
If we now add a whole bunch of new rows to the table so that the leaf block can no longer hold all the index entries, thereby forcing the index to block split and grow:
SQL> insert into same_root select rownum+1, ‘David Bowie’ from dual connect by level <=100000;
100000 rows created.
SQL> commit;
Commit complete.
And now take a block dump of the same index block:
SQL> alter system dump datafile 5 block 107442;
System altered.
Branch block dump
=================
header address 98959940=0×5e60244
kdxcolev 2
KDXCOLEV Flags = – - -
kdxcolok 0
kdxcoopc 0×80: opcode=0: iot flags=— is converted=Y
kdxconco 2
kdxcosdc 2
kdxconro 2
kdxcofbo 32=0×20
kdxcofeo 8016=0×1f50
kdxcoavs 7984
kdxbrlmc 21238606=0×144134e
kdxbrsno 1
kdxbrbksz 8060
kdxbr2urrc 0
row#0[8038] dba: 21238607=0×144134f
col 0; len 11; (11): 44 61 76 69 64 20 42 6f 77 69 65
col 1; len 5; (5): 01 41 be 5f 01
row#1[8016] dba: 21238769=0×14413f1
col 0; len 11; (11): 44 61 76 69 64 20 42 6f 77 69 65
col 1; len 5; (5): 01 44 12 ba 01
—– end of branch block dump —–
We notice that the block is no longer a leaf block but has changed itself into an index branch block.
But not just any branch block. If we now take a new treedump:
SQL> ALTER SESSION SET EVENTS ‘immediate trace name treedump level 67721′;
Session altered.
branch: 0×141a3b2 21078962(0: nrow: 3, level: 2)
branch: 0×144134e 21238606 (-1: nrow: 161, level: 1)
leaf: 0×141a3b3 21078963 (-1: nrow: 179 rrow: 179)
leaf: 0×141a3b4 21078964 (0: nrow: 179 rrow: 179)
leaf: 0×141a3b5 21078965 (1: nrow: 179 rrow: 179)
leaf: 0×141a3b6 21078966 (2: nrow: 179 rrow: 179)
leaf: 0×141a3b7 21078967 (3: nrow: 179 rrow: 179)
….
The above partial listing of the treedump clearly shows that the index has grown from a level 0 index to a level 2 index and that the root block is the very same index block as the original leaf block listed as it has the same relative block address as before (21078962).
Indeed the original leaf block is now the index root block which is still the same block that immediately follows the index segment header.
Because Oracle doesn’t have to visit the index segment header and can simply directly access the block following the index segment header as this block is always the first index block of interest when performing an index scan, the index scan has that little advantage over the FTS. And it’s this little advantage that can give the index scan the edge over a FTS, even if we’re potentially accessing data from a very small table.
And you can’t get much smaller than a table that has all it’s rows in the one table block.
So far though, the example I’ve shown has been a “normal”, everyday, non-unique index that has a 1 consistent get advantage over the FTS when accessing a row of interest. I’ll next discuss how indexes can having an even bigger edge and more significant advantage over a FTS of a tiny table, than just the 1 consistent get …
Indexes On Small Tables Part III (Another Brick In The Wall Part III) April 29, 2009
Posted by Richard Foote in Non-Unique Indexes, Oracle Indexes, Small Indexes.7 comments
So far, in Part I and Part II of this little series, we’ve looked at how Oracle performs a Full Table Scan (FTS) when accessing a small table. With very small tables, Oracle needs to still access the table segment header and perform a number of fetch operations, even if the table is as small as you can get with all the rows in the table residing in one table block and even if we’re only interested in accessing the one row. In the little example we went through, Oracle still needs to access 2 distinct table blocks and perform 4 consistent get operations to read this one row of interest.
OK, let’s now finally introduce an index into the discussion. To begin with, we’ll just create a standard, Non-Unique index on the ID column.
SQL> create index small_id_i on small(id);
Index created.
Now surely, there would be no real benefit in creating such an index on such a tiny table. This would be a separate index segment from the existing table segment so that if Oracle were to ever use the index to retrieve rows, it would need to visit both the index and table segments.
How can that possibly be more efficient than reading directly from the only table block in the table segment that contains rows ?
Well, let’s see what happens:
SQL> select * from small where id = 42;
ID NAME ---------- ----- 42 BOWIE -------------------------------------------- |Id|Operation |Name | -------------------------------------------- | 0|SELECT STATEMENT | | | 1| TABLE ACCESS BY INDEX ROWID|SMALL | |*2| INDEX RANGE SCAN |SMALL_ID_I| -------------------------------------------- Statistics ------------------------------------------- 0 recursive calls 0 db block gets 3 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
Well, we notice 2 very interesting points.
Firstly, the CBO does indeed decide to use the index, even though the table is so tiny.
Secondly, we notice that the actual number of consistent gets has actually reduced from 4 with the FTS down to just 3. The index scan does indeed require less consistent gets than the equivalent FTS.
If we look at the consistent get stats via another session before and after the SELECT staement as we did in Part I, we can again confirm only 3 consistent gets were necessary:
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 110 consistent gets - examination 25
And again after the SELECT on the small table:
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 113 (+3) consistent gets - examination 25
The consistent gets indeed increased by 3 rather than 4 but again as in the FTS example, none of these consistent gets were the cheaper “consistent gets – examination” (to be discussed later).
But why did the index scan only require 3 consistent gets ? Again, by flushing the buffer cache and tracing the session, we get the necessary information to answer the question:
SQL> alter system flush buffer_cache;
System altered.
SQL> exec dbms_monitor.session_trace_enable(waits=> true);
PL/SQL procedure successfully completed.
SQL> select * from small where id = 42;
ID NAME ---------- ----- 42 BOWIE
SQL> exec dbms_monitor.session_trace_disable();
PL/SQL procedure successfully completed.
If we now look at an example trace file:
=====================
PARSING IN CURSOR #2 len=33 dep=0 uid=88 oct=3 lid=88 tim=24452815463 hv=2225904586 ad=’23bdd320′ sqlid=’7pjs08q2at6ya’
select * from small where id = 42
END OF STMT
PARSE #2:c=0,e=7613,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,tim=24452815457
EXEC #2:c=0,e=42,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,tim=24452815593
WAIT #2: nam=’SQL*Net message to client’ ela= 5 driver id=1111838976 #bytes=1 p3=0 obj#=12446 tim=24452815631
WAIT #2: nam=’db file sequential read’ ela= 17035 file#=7 block#=118026 blocks=1 obj#=99587 tim=24452832933
WAIT #2: nam=’db file sequential read’ ela= 5592 file#=7 block#=117898 blocks=1 obj#=99586 tim=24452838643
FETCH #2:c=0,e=23057,p=2,cr=2,cu=0,mis=0,r=1,dep=0,og=1,tim=24452838728
WAIT #2: nam=’SQL*Net message from client’ ela= 394 driver id=1111838976 #bytes=1 p3=0 obj#=99586 tim=24452839197
FETCH #2:c=0,e=22,p=0,cr=1,cu=0,mis=0,r=0,dep=0,og=1,tim=24452839275
STAT #2 id=1 cnt=1 pid=0 pos=1 obj=99586 op=’TABLE ACCESS BY INDEX ROWID SMALL (cr=3 pr=2 pw=2 time=0 us cost=2 size=9 card=1)’
STAT #2 id=2 cnt=1 pid=1 pos=1 obj=99587 op=’INDEX RANGE SCAN SMALL_ID_I (cr=2 pr=1 pw=1 time=0 us cost=1 size=0 card=1)’
WAIT #2: nam=’SQL*Net message to client’ ela= 3 driver id=1111838976 #bytes=1 p3=0 obj#=99586 tim=24452839399
The critical part here is the first “db file sequential read” wait event. By looking at the obj#=99587, we can determine this refers to the SMALL_ID_I index segment. By looking at the associated file#=7 block#=118026, a block dump reveals this to be the index leaf block containing the index row entries. Notice how we do not actually access the index segment header at all prior to accessing the leaf block of the index.
The following “db file sequential read” wait event on obj#=99586 corresponds to the table segment and file#=7 blockblock#=117898 corresponds to the table block containing the rows. So the first FETCH uses 2 consistent reads (cr=2) to return the first (and in this case only) row of interest (r=1) by first reading the index leaf block and then reading the table data block containing the row of interest.
Oracle then performs another FETCH to read the leaf block again to determine whether there are any other rows of interest (cr=1) of which there are none (r=0).
There is no PK or Unique constraint on the ID column in table and the index is Non-Unique so there is no possible way for Oracle to know there is indeed only one row of interest hence why Oracle needs to again visit the leaf block just in case there are multiple rows that have an ID = 42.
So that’s our 3 consistent gets, 1 to read the leaf block, 1 to read the table block and 1 to again read the leaf block just in case there are other rows of interest.
Now in our specific example, that’s a saving of 1 consistent get and effectively 2 latch gets by using the Non-Unique index vs. performing the FTS on this effectively 1 block table. The savings would of course be more substantial if the small table had more than just the one data block containing rows.
Now you might well say, is it really worth the effort, what’s 1 consistent get in the grand scheme of things. Well, that’s 1 consistent get each and every time such a query needs to access this specific table. And this is just one table, you may have many such lookup tables in your databases. In heavily used applications, such lookup tables can potentially be accessed many millions of times per hour. Now all this can add up to potentially many millions of reduced consistent gets, many millions of reduced latch requests, which all in turn can add up to considerable CPU savings as well as the significantly reduced contention issues, increasing the general scalability of your applications.
You bet it could be worth the effort.
And as we’ll see, we can actually do a lot better than saving just 1 consistent get by using an index to access such small tables …
However, the key question at this point is why doesn’t Oracle have to visit the index segment header as it does with the table segment during a FTS and how does Oracle know where to find the index block it needs ?
That will be answered in the next instalment coming soon …
AUSOUG 3 ACEs Database Education Day – Perth 11th May 2009 April 28, 2009
Posted by Richard Foote in Richard Presentations, Richard's Musings.2 comments
Just a short note to say I’ll be presenting a 3 hour morning session on all things Oracle indexes at the 3 ACEs Database Education Day in Perth, Australia on 11th May 2009, presented by the Australian Oracle User Group.
Also presenting will be the rather clever Conner McDonald on an “Introduction To RAC” and the also equally rather clever Penny Cookson on “Bind Peeking – The Endless Tuning Nightmare”, so it should be a great day.
And the best news of all is that the event is free for all AUSOUG members with lunch provided. So for those of you lucky enough to get to Perth, follow the link above to book early and guarantee a place.
Also just want to quickly mention that I had the great pleasure of attending Tanel Poder’s “Advanced Oracle Troubleshooting Seminar” in Melbourne last week.
It’s a great seminar that I highly recommend. Although it certainly covers some “advanced” topics, I would actually recommend the seminar to not just experienced DBAs as advertised, but really any DBA who is serious about diagnosing and troubleshooting database related problems. Because by far the most important message from the seminar is that rather than guessing and hoping a database problem might be this or that, rather that hoping increasing or changing this or that parameter might help, rather than crossing your fingers and praying that moving all indexes into a larger blocksize might improves things as it appeared to when you tried it once before, why not actually use database and O/S instrumentation to systematically, accurately and reliably determine exactly what the issue really is and so immediately go about applying a solution that will actually address the real problem. The earlier a DBA learns this basic principle rather than relying on outdated and ultimately inefficient and ineffective “hit and miss” checklists, the earlier one will ultimately become an efficient and effective DBA.
Why guess when you can know …
And Tanel’s seminar provides some great advice, suggestions and the odd script or two (actually many) to go about systematically determining what really is wrong with your database and why it’s causing performance issues (be it globally or to a specific user or group of users). Like I said, a highly recommended seminar and it was great to see I’m not the only one who struggles to get through everything in a seminar in the two days !!
Indexes On Small Tables Part II (The Mysteries) April 24, 2009
Posted by Richard Foote in Oracle Indexes, Small Indexes.12 comments
I’m a bit pushed for time at the moment but I thought I might quickly just expand a little on the observations in Part I of this no doubt soon to be epic series (there’s at least another 3 parts to come) !!
In Part I we saw how even with a tiny table that consists of just one block containing 100 rows of application data, Oracle requires 4 consistent get operations to retrieve just the one row via a Full Table Scan (FTS). This surprises some folks as they expect Oracle to perhaps only need the 1 or maybe 2 consistent gets to access this one block containing application related row data.
If we were to flush the buffer cache before running the SELECT statement and trace the associated session, the resultant trace file shows how Oracle needs to first visit the Segment Header of the table before it can read the actual table block containing the row of interest. Oracle needs to read the table segment header in order to determine what blocks need to be accessed to begin the FTS operation. In this specific case, there’s only the one data block but Oracle also has to check to ensure there indeed are no more blocks it needs to visit. Oracle also has to perform another fetch operation to confirm there are indeed no more rows it needs to return after it fetches the first (and in this case only) row.
The following should help to show what’s going on during the FTS of our little 100 row table where all 100 rows nicely resides in the one data block.
We first flush the buffer cache in order to force Oracle to perform physical I/Os (note the following example was run on 11.1.0.6).
SQL> alter system flush buffer_cache;
System altered.
Next, we trace our session …
SQL> exec dbms_monitor.session_trace_enable(waits=> true);
PL/SQL procedure successfully completed.
Before running the SELECT statement that returns our one row of interest …
SQL> select * from small where id = 42;
ID NAME
---------- -----
42 BOWIE
SQL> exec dbms_monitor.session_trace_disable();
If we look at an example trace file:
=====================
PARSING IN CURSOR #19 len=33 dep=0 uid=88 oct=3 lid=88 tim=24079367090 hv=2225904586 ad=’23bdd320′ sqlid=’7pjs08q2at6ya’
select * from small where id = 42
END OF STMT
PARSE #19:c=0,e=18142,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=1,tim=24079367080
EXEC #19:c=0,e=35,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=1,tim=24079367262
WAIT #19: nam=’SQL*Net message to client’ ela= 5 driver id=1111838976 #bytes=1 p3=0 obj#=62986 tim=24079367301
WAIT #19: nam=’db file sequential read’ ela= 16020 file#=7 block#=117897blocks=1 obj#=99586 tim=24079383391
WAIT #19: nam=’db file sequential read’ ela= 16820 file#=7 block#=117898blocks=1 obj#=99586 tim=24079416967
FETCH #19:c=15625,e=49760,p=2,cr=3,cu=0,mis=0,r=1,dep=0,og=1,tim=24079417101
WAIT #19: nam=’SQL*Net message from client’ ela= 445 driver id=1111838976 #bytes=1 p3=0 obj#=99586 tim=24079417615
FETCH #19:c=0,e=27,p=0,cr=1,cu=0,mis=0,r=0,dep=0,og=1,tim=24079417699
STAT #19 id=1 cnt=1 pid=0 pos=1 obj=99586 op=’TABLE ACCESS FULL SMALL (cr=4 pr=2 pw=2 time=0 us cost=2 size=9 card=1)’
WAIT #19: nam=’SQL*Net message to client’ ela= 6 driver id=1111838976 #bytes=1 p3=0 obj#=99586 tim=24079439168
Notice how the first highlighted wait event performs a ‘db file sequential read’ to first access the segment header as denoted by file#=7 block#=117897 in order to determine which blocks need to be read during the FTS operation. You can easily confirm the file# and block # corresponds to the table segment header by querying DBA_SEGMENTS.
This is then immediately followed by another ‘db file sequential read’ wait event to access the only data block of interest as denoted by file#=7 block#=117898. Notice how this block is simply the block that follows the segment header, as all 100 rows were inserted at one time by the one transaction. Note this is the only data block in the table that contains rows and is the only table block that needs to be accessed during the FTS operation.
Notice how the first FETCH operation resulted in 3 consistent gets (cr=3), 2 consistent gets that correspond to the 2 physical I/O waits events already identified plus an extra consistent read to confirm there were no more table blocks of interest. This FETCH returns the first and (in this case) only row of interest (r=1).
A second FETCH was required resulting in an additional consistent get (cr=1) to confirm to the client that there are indeed no more rows of interest to be returned after the first row was fetched (r=0). We might know there’s only one row but Oracle doesn’t until it performs this second fetch.
Note BTW that if the query returned no rows at all, this second fetch would not have been required as the first empty fetch would have confirmed to Oracle there were no more rows to come. The total CR count would have been just 3 in this case (but would still have been bettered by an index if present).
This is a small lookup table and we’re generally interested in just the one row. As discussed in Part 1 and now expanded upon here, a FTS requires at least 4 CR behind the scenes when retrieving just the one row of interest, even if the table is tiny and can potentially store all its rows in just the one data block.
You can’t really get a table smaller than one block and yet as we’ll see, an index can beat the 4 CR overhead of reading a row from this tiny table via a FTS.
Next installment coming soon …
Indexes On Small Tables Part I (One Of The Few) April 16, 2009
Posted by Richard Foote in Oracle Indexes, Small Indexes.17 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 …
Australia Enters The Space Race !! (Life On Mars) April 1, 2009
Posted by Richard Foote in Richard's Musings.8 comments
Just had to share this incredible news. Makes one proud to be an Australian today !!
“Australia today announced the truly exciting news that starting 2010, it will begin its own space program with the ultimate aim of potentially collonising the planet Mars. This will be the first time a country has specifically stated its intention to lay claims to a territory outside the planet Earth.
Prime Minister Kevin Rudd, currently overseas and about to attend the G-20 summit, said in a press statement that as part of yet another stimulus package to help address the current financial crisis, Australia will invest 20 billion Australian Dollars over the next 15 years, in what will officially be called the Australian Research Space Enterprise. Kevin Rudd claims that not only will this investment create real jobs and help to dramatically stimulate the Australian economy, but by the program’s end, Australia will hopefully also have something incredible to show for it all as well, a big beautiful red planet to claim our own !!
Realistically, experts claim the chances of completing the project in just 15 years is going to be extremely difficult. Although most of the technology to achieve such an incredible objective is already well established within Australia, actually fully developing and building the required infrastructure and the necessary space crafts will be a difficult challenge for a country like Australia. Dr. Sharp from the Australian National University (ANU) however believes the whole enterprise is totally achievable. “We have the natural resources, we have the necessary skills and human resources, we have the climate and natural landscape and we have a country full of people who are natural battlers and who don’t know how to give up, of course we have every chance of success. We’re also used to driving long distances which can’t hurt our chances”.
Industries spokesmen and Australian businesses are fully supportive of the governments announcement and believe it will have a positive impact on many Australian businesses and on the Australian ecconnmy generally. Mr Pennywise from the Australian Business Council (ABC) believes it will help ailing Australian businesses enormously. “We needed a huge injection not only in money and investments but in confidence as well. This is the biggest bloody syringe we could have hoped for ! The resource sector, manufacturing, IT, even tourism and retail will benefit enormously from this initiative. I’m going to buy an “Aussie Aussie Aussie, Mars Mars Mars” t-shirt as soon as they’re available”.
Like Antarctica, where Australia has a 35% claim in the territory, claiming territory rights for the planet Mars is being viewed by many in Australia as a natural extension of Australia’s colonising past. Dr. Frost who is currently working at the Australian Mawson station in Antarctica thought Australia was going one better with it’s space program announcement. “Don’t get me wrong, Antarctica is a fantastic place and has many wonderful discoveries. However, at the end of the day, it’s bloody cold here, it’s stinks of Penguins and there’s not much we can do except watch the occasional Japanese ship harpoon the odd whale. Mars offers far more in terms of potential, both in materials and general resources and in preventing dry skin and chapped lips from this cold wind”.
Foreign Affairs minister Stephen Smith said in a statement “Hopefully, Australia will be viewed as providing strong leadership and guidance on how other countries can invest massive amounts of money in an economic stimulous package, that not only achieves the goal of spending massive amounts of money but also has something significant to show for all the massive amounts of money that is spent. There is nothing stopping other countries in likewise having a dream and investing massive amounts of money in achieving the dream. Yes, the dream may not actually be realised in the end but at least you tried to achieve your dreams while spending the massive amounts of money necessary in a worthwhile economic stimulus package. Other countries too can spend massive amounts of money in attempting to colonize a planet. Not Mars, cause it’s now taken but Venus is still up for grabs”.
Overseas reaction to the news has been mixed. Some countries such as New Zealand appear to view the whole initiative with some skepticism. “They can afford to send Australians to Mars but they can’t afford a decent rugby team” said one Wellington local. The UK was much more supportive with Prime Minister Brown saying “The British wish Australia every success. Who knows, if successful, hopefully one day our two great nations will be able to play a test match on Mars although I suspect the atmosphere will likely result in a swinging ball and turning wicket”.
Obama, the US President who was one of the first overseas leaders to hear of the news during recent talks with Kevin Rudd in Washington, was extremely supportive of the Australian initiative and expressed hopes the two countries would continue their close national ties by cooperating in their Space pioneering endeavors. In a press statement, President Obama said “Hopefully, we can work together in space and the US wishes Australia and its people every success in their space exploration endeavours. As a sign of our friendship and support, the US will provide funds and technical support in helping get the Australian Research Space Enterprise off the ground, with the hope that Australia might perhaps one day help to install a barbecue on the International Space Station”.
Life is likely going to be very different for many Australians with many viewing today’s official announcement with excitement and shock. “This is better than when we got the Sydney Olympics” said Manly resident Michael Star. “Instead of being the centre of the world for two weeks, we’re going to be the centre of the world for bloody years. Well, the centre of Mars anyways”. “We’re going to Mars, we’re going to Mars” screamed school girl Nancey Moon from Canberra. John Mercury, a homeless man from Brisbane was less exuberant at hearing the news “Who gives a flying shit about @#$%ing Mars, there’s no decent pubs there last time I checked”.
State lotteries are already planning a national selection lottery that will select two lucky “everyday Australians” to be part of the first official flight to Mars. Not only will you need to have a good head for heights, but also be able to pick 6 straight numbers plus one supplementary. There will also be a national contest to select which beer will be the first beer in space and another competition aimed at school children to decide what the bumper sticker should be on the first Aussie spaceship to Mars. Although way too early to tell, “If you can read this sticker, you’re way too close and you’ll instantly be burnt to death once we take off you dick head” is already an early favourite !! “.


