Merry Christmas and A Happy New Year !! (The Snowman) December 24, 2012
Posted by Richard Foote in Christmas.1 comment so far
I just like to wish you all a very Merry Christmas and a Happy, Index-Rebuild Free New Year !!
I have a feeling next year will very exciting one for Oracle Database professionals:)
My little “gift” this year is a lovely little animation called “The Snowman” that dates all the way back to 1982 when I arrived in Australia.
Introduced by the one and only David Bowie, it’s based on the famous children’s book by Raymond Biggs (who also did “When The Wind Blows” that Bowie also worked on).
Enjoy !!
5 Year Anniversary (5 Years) December 20, 2012
Posted by Richard Foote in Richard's Musings.13 comments
It’s just occurred to me that I’ve just recently past the 5 year anniversary of when I started this humble little blog !!
My first real post on Invisible Indexes was written back on 11 December 2007. Some 256 posts later and I’m still here, posting occasionally on Oracle Indexes with as many David Bowie references as I can manage 🙂
There have been roughly 923,000 page views in that time and more importantly some 3,000+ comments in total, some of them driving shall we say “interesting and lively” debates. The entry with the most comments is this one on how execution plans can suddenly when the statistics remain unchanged, although this one on rebuilding indexes every Sunday afternoon was up there as well.
The most views on any given day was back on March 13, 2010 with 3606 views, when this entry on block and tree dumps was particularly hammered. The most popular blog entry of all time however goes to this one on the change of behaviour of dbms_stats with 10g, with this one on the differences between an index rebuild, coalesce and shrink a very close second.
My all-time favourite blog entry however is this April Fool’s one on my retirement announcement and plans to work in the adult film industry. Some of the comments on this one are just classic 🙂
A very sincere thank-you to all my loyal readers and to everyone who has contributed to this blog over the years, I hope you’ve learnt something new along the way. I certainly have !! With the Oracle 12c database due for release sometime in 2013, I’m sure there will be lots to write about for a while longer yet 🙂
“Pushing through the market square, so many mothers sighing, news had just come over we had 5 years left to cry in”
Storage Indexes vs Database Indexes Part II: Clustering Factor (Fast Track) December 19, 2012
Posted by Richard Foote in Clustering Factor, Exadata, Oracle Indexes, Storage Indexes.3 comments
Two posts in two days !! Well, with Christmas just around the corner, I thought I better finish off a couple of blog posts before I get fully immersed in the festive season 🙂
The Clustering Factor (CF) is the most important index related statistic, with the efficiency of an index performing multi-row range scans very much dependent on the CF of the index. If the data in the table is relatively well clustered in relation to the index (i.e. it has a “low” CF), then an index range scan can visit relatively few table blocks to obtain the necessary data. If the data is effectively randomised and not well clustered in relation to the index (i.e. has a “high” CF), then an index range scan has to visit many more table blocks and not be as efficient/effective as a result. The CBO will be less inclined to use such an index as a result, depending on the overall selectivity of the query.
It’s something I’ve discussed here many times before.
It’s a very similar story for Exadata Storage Indexes (SI) as well. The better the table data is clustered in relation to the SIs, the more efficient and effective the SIs are likely to be in relation to being able to eliminate accessing storage regions that can’t possibly contain data of interest. By having the data more clustered (or ordered) in relation to a specific SI, the Min/Max ranges associated with the SI are more likely to be able to determine areas of the table where data can’t exist.
For the data I’ll be using in the following examples, I refer you to the previous post where I setup the necessary data.
The following query is on a 10 million row table, based on the ALBUM_ID column that has an excellent CF:
SQL> select * from big_bowie where album_id = 42; 100000 rows selected. Elapsed: 00:00:01.07 Execution Plan ---------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100K| 8984K| 1550 (1)| 00:00:19 | | 1 | TABLE ACCESS BY INDEX ROWID| BIG_BOWIE | 100K| 8984K| 1550 (1)| 00:00:19 | |* 2 | INDEX RANGE SCAN | BIG_BOWIE_ALBUM_ID_I | 100K| | 199 (1)| 00:00:03 | ---------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("ALBUM_ID"=42) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 1590 consistent gets 1550 physical reads 0 redo size 9689267 bytes sent via SQL*Net to client 733 bytes received via SQL*Net from client 21 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 100000 rows processed
An index range scan access path is selected by the CBO to retrieve the 100,000 rows. At just 1590 consistent gets, with such an excellent CF, the index can very efficiently access just the necessary 100,000 rows of data. Notice that most of these consistent gets are still physical reads (1550). If we re-run the query several times and are able to cache the corresponding index/table blocks in the database buffer cache:
SQL> select * from big_bowie where album_id = 42; 100000 rows selected. Elapsed: 00:00:00.27 Execution Plan ---------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100K| 8984K| 1550 (1)| 00:00:19 | | 1 | TABLE ACCESS BY INDEX ROWID| BIG_BOWIE | 100K| 8984K| 1550 (1)| 00:00:19 | |* 2 | INDEX RANGE SCAN | BIG_BOWIE_ALBUM_ID_I | 100K| | 199 (1)| 00:00:03 | ---------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("ALBUM_ID"=42) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 1590 consistent gets 0 physical reads 0 redo size 9689267 bytes sent via SQL*Net to client 733 bytes received via SQL*Net from client 21 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 100000 rows processed
The overall execution times reduce down from 1.07 to just 0.27 seconds. Not bad at all considering we’re returning 100,000 rows.
However, if we run the same query on the same data with all the smarts turned on in Exadata (with the index made Invisible so that it doesn’t get used by the CBO):
SQL> select * from big_bowie where album_id = 42; 100000 rows selected. Elapsed: 00:00:00.27 Execution Plan --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100K| 8984K| 36663 (1)| 00:07:20 | |* 1 | TABLE ACCESS STORAGE FULL| BIG_BOWIE | 100K| 8984K| 36663 (1)| 00:07:20 | --------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - storage("ALBUM_ID"=42) filter("ALBUM_ID"=42) Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 134834 consistent gets 134809 physical reads 0 redo size 4345496 bytes sent via SQL*Net to client 73850 bytes received via SQL*Net from client 6668 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 100000 rows processed SQL> select name , value/1024/1024 MB from v$statname n, v$mystat s where n.statistic# = s.statistic# and n.name in ('cell physical IO interconnect bytes returned by smart scan', 'cell physical IO bytes saved by storage index'); NAME MB ---------------------------------------------------------------- ---------- cell physical IO bytes saved by storage index 1042.24219 cell physical IO interconnect bytes returned by smart scan 9.56161499
We notice that although a Full Table Scan is being performed, the overall performance of the query is practically identical to that of using the index. That’s because the SIs are kicking in here and by saving 1042 MB (approximately 99% of the table), Oracle only has to actually physically access a tiny 1% of the table (basically, the 1% selectivity of the query itself). SIs based on the well clustered ALBUM_ID column are therefore very effective at eliminating the access of unnecessary data.
If we now run a query based on the TOTAL_SALES column in which the data is randomly distributed all over the place and so the associated index has a very poor CF:
SQL> select album_id, artist_id from big_bowie where total_sales between 42 and 142; 2009 rows selected. Elapsed: 00:00:01.45 Execution Plan ------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2040 | 26520 | 2048 (1)| 00:00:25 | | 1 | TABLE ACCESS BY INDEX ROWID| BIG_BOWIE | 2040 | 26520 | 2048 (1)| 00:00:25 | |* 2 | INDEX RANGE SCAN | BIG_BOWIE_TOTAL_SALES_I | 2040 | | 7 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("TOTAL_SALES">=42 AND "TOTAL_SALES"<=142) Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 2150 consistent gets 2005 physical reads 0 redo size 43311 bytes sent via SQL*Net to client 1987 bytes received via SQL*Net from client 135 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 2009 rows processed
We notice that although only 2009 rows are retrieved with this query, 2150 consistent gets have been performed (practically 1 for each row returned) . This is somewhat more than the 1590 consistent gets of the previous example when a full 100,000 rows were returned. Using this index therefore is nowhere near as efficient/effective in retrieving data as was the index in the previous example.
If all this data can be cached in the buffer cache however, we can again improve overall execution times:
SQL> select album_id, artist_id from big_bowie where total_sales between 42 and 142; 2009 rows selected. Elapsed: 00:00:00.02 Execution Plan ------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes |Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 2040 | 26520 | 2048 (1)| 00:00:25 | | 1 | TABLE ACCESS BY INDEX ROWID| BIG_BOWIE | 2040 | 26520 | 2048 (1)| 00:00:25 | |* 2 | INDEX RANGE SCAN | BIG_BOWIE_TOTAL_SALES_I | 2040 | | 7 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("TOTAL_SALES">=42 AND "TOTAL_SALES"<=142) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 2150 consistent gets 0 physical reads 0 redo size 43308 bytes sent via SQL*Net to client 1987 bytes received via SQL*Net from client 135 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 2009 rows processed
So with all the index/table data now cached, we can return the 2000 odd rows in just 0.02 seconds.
If we now run the same query with the same data in Exadata:
SQL> select album_id, artist_id from big_bowie where total_sales between 42 and 142; 2009 rows selected. Elapsed: 00:00:01.25 Execution Plan --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2040 | 26520 | 36700 (1)| 00:07:21 | |* 1 | TABLE ACCESS STORAGE FULL| BIG_BOWIE | 2040 | 26520 | 36700 (1)| 00:07:21 | --------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - storage("TOTAL_SALES"<=142 AND "TOTAL_SALES">=42) filter("TOTAL_SALES"<=142 AND "TOTAL_SALES">=42) Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 134834 consistent gets 134809 physical reads 0 redo size 47506 bytes sent via SQL*Net to client 1987 bytes received via SQL*Net from client 135 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 2009 rows processed SQL> select name , value/1024/1024 MB from v$statname n, v$mystat s where n.statistic# = s.statistic# and n.name in ('cell physical IO interconnect bytes returned by smart scan', 'cell physical IO bytes saved by storage index'); NAME MB ---------------------------------------------------------------- ---------- cell physical IO bytes saved by storage index 72.65625 cell physical IO interconnect bytes returned by smart scan .383415222
We noticed that the physical IO bytes saved by the SIs has significantly reduced from the previous example (just 72 MBs down from 1042 MBs), even though at just 2000 odd rows we require much less data than before. In this example, only approximately 7% of table storage need not be accessed, meaning we still have to access a significant 93% of the table as the required data could potentially exist throughout the majority of the table. The poor clustering of the data in relation the TOTAL_SALES column has effectively neutralised the effectiveness of the associated SIs on the TOTAL_SALES column.
Note also that the 1.25 seconds is as good as it gets when performing a FTS with the fully generated SIs in place. In this case, using a fully cached database index access path can outperform the FTS/SI combination and provide a more efficient and ultimately more scalable method of accessing this data. As the required selectively on this column is low enough to warrant the use of a database index despite the poor CF, this is again another example of an index we may not necessarily want to automatically drop when moving to Exadata.
Storage Indexes vs Database Indexes Part I MIN/MAX (Maxwell’s Silver Hammer) December 18, 2012
Posted by Richard Foote in Exadata, Oracle Indexes, Storage Indexes.1 comment so far
It’s often stated that in Exadata, you don’t need conventional database indexes anymore as everything runs so damn fast that indexes are simply a waste of time and space. Simply drop all database indexes and things will run just as fast.
Well, not quite …
There are many many scenarios where database indexes are still critical for optimal performance and scalability in an Exadata environment.
Prompted by a question in an earlier post (thanks Paul), I thought I might start looking at some scenarios where dropping a database index in Exadata would not be the best of ideas.
The first example is the scenario where one wants either the minimum/maximum of a column value. As database index entries are always ordered, this can very efficiently be found by traversing down to either the first or last index leaf block within an appropriate index. There’s an execution the INDEX FULL SCAN MIN/MAX execution path by which the CBO can decide to access an index in this manner.
For example, if I create an index on the ID column of my large DWH_BOWIE table and select the MIN(ID):
SQL> create index dwh_bowie_id_i on dwh_bowie(id); Index created. SQL> select min(id) from dwh_bowie; MIN(ID) ---------- 1 Elapsed: 00:00:00.01 Execution Plan --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 6 | 4 (0)| 00:00:01 | | 1 | SORT AGGREGATE | | 1 | 6 | | | | 2 | INDEX FULL SCAN (MIN/MAX)| DWH_BOWIE_ID_I | 1 | 6 | 4 (0)| 00:00:01 | --------------------------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 4 consistent gets 3 physical reads 0 redo size 525 bytes sent via SQL*Net to client 524 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
With only a handful of consistent gets, we have managed to get the minimum ID value extremely quickly and efficiently.
However, if we drop this index and re-run the same query in Exadata numerous times:
SQL> select min(id) from dwh_bowie; MIN(ID) ---------- 1 Elapsed: 00:00:41.31 Execution Plan ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 6 | 2345K (1)| 07:49:12 | | 1 | SORT AGGREGATE | | 1 | 6 | | | | 2 | TABLE ACCESS STORAGE FULL| DWH_BOWIE | 640M| 3662M| 2345K (1)| 07:49:12 | ---------------------------------------------------------------------------------------- Statist5ics ---------------------------------------------------------- 0 recursive calls 0 db block gets 8626963 consistent gets 8625479 physical reads 0 redo size 525 bytes sent via SQL*Net to client 524 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed NAME MB ---------------------------------------------------------------- ---------- cell physical IO bytes saved by storage index 0 cell physical IO interconnect bytes returned by smart scan 7644.38194
We notice the query takes considerably longer and that the Storage Indexes have been unable to be of any help with not a single byte saved.
This is because all aggregation type operations are performed at the database level, not within the storage servers. Even though in theory Storage Indexes could be seen as perhaps being of value here, they are totally ignored by Oracle when retrieving either the minimum/maximum of a column value. Remember also that a key difference between a Storage Index and a Database Index is that a Storage Index does not necessarily have to exist fully for all regions of a given table.
Drop database indexes used in this manner at your peril …
Many more examples to come 🙂
Exadata Storage Indexes Part V: Warming Up (Here Come The Warm Jets) December 3, 2012
Posted by Richard Foote in Exadata, Oracle Indexes, Storage Indexes.2 comments
As I mentioned in a previous post, there are a number of Similarities between Storage Indexes and Database Indexes.
One of these similarities is the “warming up” process that needs to take place before indexes become “optimal” after either the Storage Server (in the case of Storage Indexes) or the Database Server (in the case of Database Indexes) is restarted.
When a database server is restarted, the contents associated with the database buffer cache is lost and has to be reloaded (as well as other components of the SGA of course). This means for a period of time while the instance “warms up”, there is a spike in physical IO (PIO) activity. Index range scans, which generally greatly benefit from re-using cached data therefore need to perform additional PIOs for both the index blocks and the table blocks they reference. The overheads associated with these additional PIOs in turn slows the overall performance of (especially) these index related execution plans.
As a simple illustration, the following query is executed after a flushed buffer cache:
SQL> select * from big_bowie where album_id = 42; 1000 rows selected. Elapsed: 00:00:01.99 Execution Plan ---------------------------------------------------------- Plan hash value: 2703824528 ---------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000 | 75000 | 101 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| BIG_BOWIE | 1000 | 75000 | 101 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | BIG_BOWIE_ALBUM_ID_I | 1000 | | 1 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("ALBUM_ID"=42) Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 1072 consistent gets 1005 physical reads 0 redo size 91231 bytes sent via SQL*Net to client 1245 bytes received via SQL*Net from client 68 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1000 rows processed
There is a considerable amount of physical reads associated with having to re-load the contents of the database buffer cache. If we now re-run the query:
SQL> select * from big_bowie where album_id = 42; 1000 rows selected. Elapsed: 00:00:00.01 Execution Plan ---------------------------------------------------------- Plan hash value: 2703824528 ---------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1000 | 75000 | 101 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| BIG_BOWIE | 1000 | 75000 | 101 (0)| 00:00:01 | |* 2 | INDEX RANGE SCAN | BIG_BOWIE_ALBUM_ID_I | 1000 | | 1 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("ALBUM_ID"=42) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 1072 consistent gets 0 physical reads 0 redo size 91231 bytes sent via SQL*Net to client 1245 bytes received via SQL*Net from client 68 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1000 rows processed
All the data is now cached and the overall query response time improves considerably. Once a database instance has warmed up and much of the database’s hot, data-set of interest has been cached, PIOs are minimised and performance improves accordingly.
Much has been made of the fact Storage Indexes (SIs) need to be created on the fly but in a similar manner to database indexes, they just need a period of time to warm up and be created/stored in memory to become optimal. The difference being that SIs are actually created on the fly when cached in memory unlike database indexes which physically preexist on disk.
So when an Exadata storage server is re-started, the performance associated with the SIs on the server is initially sluggish as well, while Oracle performs the additional PIOs off disk to create the necessary SIs in memory. Additionally, new queries using new column predicates will also be initially sluggish while any necessary SIs are created and cached in memory. Note that SIs are created on a 1M storage region basis so it could well take a period of time for a SI to be created across all the associated storage regions of the table. There is also a limit of 8 SIs per 1M storage region which might limit whether a SI is actually created or preserved (more on this point in a later post).
The following query is executed on a relatively new table and in a new session to capture fresh session statistics:
SQL> select artist_id, total_sales from big_bowie where album_id = 42; 100000 rows selected. Elapsed: 00:00:00.89 Execution Plan --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100K| 1269K| 36663 (1)| 00:07:20 | |* 1 | TABLE ACCESS STORAGE FULL| BIG_BOWIE | 100K| 1269K| 36663 (1)| 00:07:20 | --------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - storage("ALBUM_ID"=42) filter("ALBUM_ID"=42) SQL> select name , value/1024/1024 MB from v$statname n, v$mystat s where n.statistic# = s.statistic# and n.name in ('cell physical IO interconnect bytes returned by smart scan', 'cell physical IO bytes saved by storage index'); NAME MB ---------------------------------------------------------------- ---------- cell physical IO bytes saved by storage index 114.601563 cell physical IO interconnect bytes returned by smart scan 1.77637482
The amount of physical IO bytes saved by a SI is initially minimal, about 114 MB.
However, when the query is re-executed and the associated SI on the ALBUM_ID column has been fully created:
SQL> select artist_id, total_sales from big_bowie where album_id = 42; 100000 rows selected. Elapsed: 00:00:00.27 Execution Plan --------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 100K| 1269K| 36663 (1)| 00:07:20 | |* 1 | TABLE ACCESS STORAGE FULL| BIG_BOWIE | 100K| 1269K| 36663 (1)| 00:07:20 | --------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - storage("ALBUM_ID"=42) filter("ALBUM_ID"=42) SQL> select name , value/1024/1024 MB from v$statname n, v$mystat s where n.statistic# = s.statistic# and n.name in ('cell physical IO interconnect bytes returned by smart scan', 'cell physical IO bytes saved by storage index'); NAME MB ---------------------------------------------------------------- ---------- cell physical IO bytes saved by storage index 1156.84375 cell physical IO interconnect bytes returned by smart scan 3.41764832
The amount of physical IO bytes saved by SIs increases considerably and overall performance improves as a result.
Restarting a database server will result in sluggish performance for a period until most of the database hot data is cached. This is also the case when an Exadata Storage server is re-started, in part while the necessary SIs are recreated and cached.
I’ll next discuss another similarity, that being the importance of the clustering of data within the table to the efficiency of these indexes.