Archive for the ‘10gR2’ Category.

Saving Rows from Corrupt Blocks

Recently we ran into a database with a (or some) incomplete transactions that had not been able to rollback due to a file in the undo tablespace that had been deleted. And as luck would have it, the log files were some how lost as well (so we couldn’t just recover the undo file). So the database was up and running, but the undo file was missing and any time you hit one of the records that needed info from the missing undo file to rebuild a consistent version, it would fail with a ORA-00376  error (file not found). Technically that is not a corrupt block, just an uncommitted record that has been written, with missing undo. Make sense? Anyway, we narrowed it down to a couple of sub-partitions and were able to export the data from all the other sub-partitions. To get the data out of the affected sub-partitions we decided to use an approached based on the old rowid, which contained file_id,block_id, and row_num.

Toon Koppelaars did an excellent write up on this basic approach here. But it was written a while back and unfortunately the rowid was changed somewhere around the Oracle 8i time frame making it a little more difficult. However, in their infinite wisdom, the developers at Oracle added the DBMS_ROWID package which allows us to work around the issue.

So here’s the psuedo code for what we did:

Get Object Name (object_name)
Get Max Rows Per Block (max_rows_per_block)
Get List of Blocks by Extent for Object
 
For Each Extent
  For Each Block 
    For row in 1 to max_rows_per_block
      insert into saved_table select * from object_name 
        where rowid = dbms_rowid.rowid_create(file,block,row);
    End Row Loop
  End Block Loop
End Extent Loop

The actual script we used is a little more complicated. It actually created a Bad Blocks table as well so we’d know how many blocks were skipped and it had some error checking. I later embellished it a bit to make the object name dynamic (which was considerably more work than I thought it would be). Here’s the actual script I ended up with: save_u.sql. NOTE: I am not the worlds greatest pl/sql guy, so if you have any suggestions, let me know! But it seems to do the job. You may also find this script (obj_blocks.sql) useful for getting a list of all the blocks mapped to a specific object. And here’s a couple of scripts to create functions that might come in handy: create_new_rowid.sql (creates function new_rowid which returns the new format rowid if you give it the obj#, file#, block#, and row#) and create_old_rowid.sql (which returns the old format rowid if given the new format id). Here’s a quick example of the scripts in use:

> sqlplus / as sysdba
 
SQL*Plus: Release 10.2.0.3.0 - Production on Thu Feb 12 11:35:14 2009
 
Copyright (c) 1982, 2006, Oracle.  All Rights Reserved.
 
 
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Production
With the Partitioning, Real Application Clusters, OLAP and Data Mining options
 
SQL> @obj
Enter value for owner: MF
Enter value for name: BROKE%
Enter value for type: 
 
OWNER           OBJECT_NAME                    OBJECT_TYPE         STATUS  T
--------------- ------------------------------ ------------------- ------- -
MF              BROKE_NO_DEPENDENCIES          TABLE               VALID   N
MF              BROKE_YES_DEPENDENCIES         TABLE               VALID   N
 
SQL> set echo on
SQL> @save_u
Enter value for owner_name: mf
Enter value for table_name: BROKE_NO_DEPENDENCIES
 
WARNING: This script may issue a DROP TABLE command. Do not execute it unless you have read through it
and are comfortable you know what it does.
 
Ready? (hit ctl-C to quit)  
Enter value for owner_name: mf
Enter value for table_name: BROKE_NO_DEPENDENCIES    
 
 
Saved 18800 records in BROKE_NO_DEPENDENCIES_SAVED.
35 bad records in BROKE_NO_DEPENDENCIES_BAD.
 
PL/SQL procedure successfully completed.
 
SQL> set echo on
SQL> @create_old_rowid
SQL> create or replace function old_rowid (p_rowid rowid)
  2  return varchar as
  3  
  4    rowid_type NUMBER;
  5    object_id NUMBER;
  6    fileno NUMBER;
  7    blockno   NUMBER;
  8    rowno  NUMBER;
  9  
 10  BEGIN
 11  
 12     dbms_rowid.rowid_info(p_rowid, rowid_type, object_id, fileno, blockno, rowno);
 13  /*
 14     dbms_output.put_line('Row Typ-' || TO_CHAR(rowid_type));
 15     dbms_output.put_line('Obj No-' || TO_CHAR(object_id));
 16     dbms_output.put_line('RFNO-' || TO_CHAR(fileno));
 17     dbms_output.put_line('Block No-' || TO_CHAR(blockno));
 18     dbms_output.put_line('Row No-' || TO_CHAR(rowno));
 19  */
 20  return(to_char(fileno)||'.'||to_char(blockno)||'.'||to_char(rowno));
 21  
 22  END;
 23  /
 
Function created.
 
SQL> @create_new_rowid
SQL> create or replace function new_rowid (p_object_id number, p_old_rowid varchar)
  2  return varchar as
  3  
  4    new_rowid varchar2(30);
  5    fileno NUMBER;
  6    blockno   NUMBER;
  7    rowno  NUMBER;
  8  
  9  BEGIN
 10  
 11    fileno := substr(p_old_rowid,1,instr(p_old_rowid,'.')-1);
 12    blockno := substr(p_old_rowid,instr(p_old_rowid,'.')+1,instr(p_old_rowid,'.',1,2)-instr(p_old_rowid,'.'));
 13    rowno := substr(p_old_rowid,instr(p_old_rowid,'.',1,2)+1,100);
 14    new_rowid := DBMS_ROWID.ROWID_CREATE(1, p_object_id, fileno , blockno , rowno);
 15  
 16    return(new_rowid);
 17  
 18  END;
 19  /
 
Function created.
 
SQL> select rowid,old_rowid(rowid) old_rowid from mf.BROKE_NO_DEPENDENCIES where rownum < 5
SQL> /
 
ROWID              OLD_ROWID
------------------ ------------------------------
AAACYmAAEAAAAAMAAt 4.12.45
AAACYmAAEAAAACIAAt 4.136.45
AAACYmAAEAAAAAMAAF 4.12.5
AAACYmAAEAAAACIAAF 4.136.5

One other thing to keep in mind, as Toon mentioned, it may be possible to retrieve data from an index, even if the underlying data block is messed up. Selecting only the values of the indexed columns allows Oracle to completely bypass the data blocks. So, if for example, you found a set of blocks were inaccessible, you may be able to construct statements that would retrieve at least some of the data from the indexes like so:

select /*+ index (messed_up_object  mess_up_object_pk) */ indexed_column1, indexed_column2 
from messed_up_object 
where rowid in (select dbms_rowid.rowid_create(1,object,file,block,row) from bad_rows_table);

Your comments are always welcomed!

Oracle Fudge

One of my favorite holiday treats was my MeeMaw’s fudge brownies. Note: I did a brief poll (only 5 people so not statistically significant) but nevertheless, 100% of the people I surveyed had a grandparent that they called either MeeMaw or PopPa. And 40% had both a MeeMaw and a PopPa. Of course all 5 of the pollees were native Texans. Anyway, here’s what my MeeMaw’s fudge brownies looked like.

Oracle has a long history of baking fudge as well.

So here’s a little Oracle Fudge for you!

11gR1 has 4 parameters with the word fudge in them.

_nested_loop_fudge
_parallelism_cost_fudge_factor
_px_broadcast_fudge_factor
_query_rewrite_fudge

These four “fudge” parameters have been around with the same default values since at least 8.1.7. Maybe the elves will fix these in version 12.

And in keeping with the holiday theme, I’m reminded of the song “My Favorite Things” (often sung at Christmas) that goes:

“blah, blah, blah, blah, blah, blah,
These are a few of my favorite things”
(think Julie Andrews in Sound of Music)

Anyway, here’s a few of my favorite parameters (and my interpretation of what they mean):

db_cache_advice - If you turn this one on, Oracle will tell you what to do with your money.
db_cache_size - And this one will tell you how much money you have.
db_ultra_safe - Oddly enough, this one defaults to OFF. Seems like you’d want your database to be “Ultra Safe”.
ifile - Looks like someone from Apple slipped this one in (you know - iPod, iPhone, iMac, etc…).
large_pool_size - Just how big is your pool?
skip_unusable_indexes - Defaults to TRUE. I guess if you want, you can tell Oracle to use those unusable indexes.
_addm_skiprules - Yeah, rules suck!
_ash_size - Do these pants make my butt look big?
_backup_max_gap_size - How big does the doorway have to be to get your butt through it?
_asm_disk_repair_time - Uh oh, time to repair those disks.
_avoid_prepare - Why get ready ahead of time.
_awr_disabled_flush_tables - Not sure but it sounds stinky.
_awr_sql_child_limit - Population control?
_bloom_pruning_enabled - Trim the roses!
_bwr_for_flushed_pi - Not sure what this one does, but flushing pie seems like such a waste.
_cvw_enable_weak_checking - I’d prefer strong checking please! Get that weak stuff out of here!
_db_aging_cool_count - I used to be cool, I think.
_db_aging_hot_criteria - ???
_db_block_bad_write_check - I hope our database is not writing bad checks!
_db_block_check_for_debug - I think a developer from Chicago named this one (and it should be: _db_block_check_for_the_bug).
_db_cache_crx_check - I don’t know what a crx check is, but cashing any kind of check should be good, right?
_db_large_dirty_queue - Just like in the laundry room at home.
_db_row_overlap_checking - Do your rows overlap? Perhaps we should check that.
_disable_fast_aggregation - Why would anyone use this, “No thanks, I want really slow aggregation”
_dtree_pruning_enabled - Trim D-Tree too while yer at it!
_extended_pruning_enabled - Cut ‘em way back!
_disable_recoverable_recovery - Hmmmm???? I guess if you don’t want your recovery to be recoverable you can set this one.
_dummy_instance - I’ve thought this many times (it’s basically the same as the _stupid_database parameter).
_dispatcher_rate_scale - How much are we paying that dispatcher anyway?
_fairness_threshold - My queries should always run faster than everyone else’s, I think that’s fair.
_flashback_fuzzy_barrier - Fuzzy Wuzzy was a bear, Fuzzy Wuzzy had no hair, Fuzzy Wuzzy wasn’t very fuzzy was he.
_gc_defer_time - I’ve wanted to do this many times in the past.
_ges_dd_debug - Sounds like a speech impediment, b,b,but maybe not.
_hard_protection - Well if it was easy, everyone would be doing it.
_imr_avoid_double_voting - They needed this in Florida during the 2004 election.
_in_memory_undo - Same as the _forget parameter.
_kdli_STOP_dba - Keep the DBA from messing up the system.
_kdli_delay_flushes - Don’t flush until a specified threshold is reached.
_kdlwp_flush_threshold - The amount of poo that triggers flushing (see _kdli_delay_flushes).
_kdli_memory_protect - Same as the _dont_forget parameter (i.e. the opposite of the _forget parameter).
_kdli_squeeze - One of my favorite bands.
_kebm_nstrikes - The number of strikes before you are out (defaults to 3 - no joke).
_kebm_suspension_time - How long before convicted felons can return to playing football.
_kill_enqueue_blocker - What the defensive linemen try to do on every play.
_kill_java_threads_on_eoc - I always turn this one on, because anything that kills java threads is OK in my book.
_kfm_disable_set_fence - Good fences make good neighbors.
_kse_signature_limit - The cash advance limit on your credit card.
_kse_snap_ring_size
_ksi_clientlocks_enabled - Can be used on clients when they won’t follow your advice.
_lm_better_ddvictim - Not sure what this one does, but it has the word victim in it, scary!
_lm_master_weight - Set this to get control of your diet.
_lm_tx_delta - Ah the Texas delta, I think it’s some where near Galveston.
_max_exponential_sleep - The older I get, the longer the naps.
_memory_sanity_check - Do I seem crazy to you?
_mv_generalized_oj_refresh_opt - In general, orange juice is refreshing!
_olapi_memory_operation_history_retention - Same as the _dont_forget parameter (see _kdli_memory_protect).
_olap_wrap_errors - When you cut the wrapping paper too short and it won’t go around the present and you have to cut a little strip to cover the gap.
_optimizer_ignore_hints - No matter what you hear in there, no matter how cruelly I beg you, no matter how terribly I may scream, don’t open that door.
_optimizer_random_plan - This one is self evident and has defaulted to TRUE since the CBO first came out.
_optimizer_squ_bottomup - Bottoms Up!
_parallel_fake_class_pct - “The higher you hold your pinky, the fancier you are.” - Patrick from Sponge Bob.
_parallel_syspls_obey_force - The parameter that allows you to get the sysadmins (syspls) to do what you tell them.
_pct_refresh_double_count_prevented - This would have been useful in Florida during the 2004 election.
_pdml_gim_staggered - Gim must have had too much to drink.
_pred_move_around - If your dad was in the air force and you moved from base to base while growing up, setting this parameter will make you feel right at home.
_px_no_stealing - This parameter is set to TRUE by default and it’s actually against the law to change it.
_px_nss_planb - Use this parameter if plan A doesn’t work.
_shrunk_aggs_enabled - I don’t like shrunk aggs, I like the big-uns, wif bacun!
_spin_count - Have you ever seen fans at a baseball game put their heads on the end of a bat and turn circles and then run? You get the idea.
_two_pass_reverse_polish_enabled - I don’t think this one is politically correct.
_use_best_fit - One size fits all does not fit all.
_write_clones - Send a letter to your siblings.
_ultrafast_latch_statistics - Anything that is ultrafast has got to be fantastic!
_xsolapi_densify_cubes - Densify??? I think they made that word up.
_xsolapi_optimize_suppression - Allows us to to keep the common man down as effectively as possible.
_xsolapi_stringify_order_levels - Stringify??? I think they made that word up too!
_xsolapi_use_models - It will make our advertising look better!

Here a few that allow you to turn on (or off) special checks:

_disable_acid_check - My acid is just fine thanks.
_disable_cpu_check - Yep, this machine has got at least one.
_disable_health_check - I don’t need no stinking annual checkup!
_disable_image_check - And I really don’t care how I look!

Here’s a few parameters dealing with death:

_px_execution_services_enabled - Apparently we can set up a special service for executions.
_xsolapi_share_executors - And if you have a lot of killing to do, it’s wasteful to not share executors.
_imr_splitbrain_res_wait - Sounds like it would hurt (if not kill).
_ksv_spawn_control_all - Creates zombies.
_cgs_zombie_member_kill_wait - Specifies how long to wait before slaying zombies!
_imr_evicted_member_kill - Boy I hope I don’t get evicted!
_ksu_diag_kill_time - Killing Time!
_ksuitm_dont_kill_dumper - The rumor is that this one was named by an animal rights activist and it was supposed to be “Don’t Kill Thumper”.
_lm_rcvr_hang_kill - Death by hanging!
_ksv_pool_hang_kill_to - If hanging doesn’t work, drown them in the pool!
_hang_detection - Turn this on so we’ll know when anybody is getting hung.

And just in case the 2000+ parameters in 11g aren’t enough, they have a few spares:

_first_spare_parameter
_second_spare_parameter
_third_spare_parameter
_fourth_spare_parameter
_fifth_spare_parameter
_sixth_spare_parameter
_seventh_spare_parameter

 Anyway, that’s it for now. I hope you have a happy holiday and a …

Merry Christmas!

Flush a Single SQL Statement - Take 2

I posted earlier about the ability to flush a single SQL statement out of the shared pool in 11g (also back ported to 10.2.0.4 with a bit of extra work) here. If you are on an earlier release of Oracle though, you can accomplish the same thing by creating an outline on the statement using the DBMS_OUTLN.CREATE_OUTLINE procedure. I just discovered this recently, so let me know if I just missed this trick. Anyway, prior to noticing this affect of creating an outline, my best options to flush a statement were:

  • flush the shared pool - not a very appealing option in a production environment (although I see almost the same affect frequently at sites that gathering stats every night).
  • modify an object that the statement depends on - I usually would add a comment to one of the tables used by the statement. Unfortunately, all statements that use the table will be flushed, so this technique can also be a little hard on a production system, but it’s certainly better than flushing the whole shared pool. 

So I wrote a little script (like I always do) and called it flush_sql10.sql. There are a couple of things to be aware of with it.

  1. I don’t like having to find the hash_value that the create_outline procedure uses, so I prompt for the sql_id and then let the script find the hash_value.
  2. The create_outline procedure requires a child number, but the flushing affect is not limited to the specified child. All children for the specified statement will be flushed.
  3. The script drops the outline after creating it (since the purpose is to flush the statement, not to create an outline). This part is a little dicey since the create_outline procedure does not return an identifier for the outline that gets created. Nor does it allow you set a name for the outline. So I coded it to drop the most recently created outline (which should be sufficient, since it would be highly unlikely that more than one person would be creating outlines at the same time). But wait, don’t answer yet, I also limited the drop to outlines created in the last 5 seconds. Bottom line, it is unlikely that an unintended outline would be accidentally dropped by the script. (you have however been forewarned!)
  4. There’s no error checking. Any errors stop execution of the script and are passed back to the user. The most common error is to not give it a valid SQL_ID, CHILD_NO combination. In this case the create_outline procedure fails and the script exits with a “no data found” message.

Here’s an example:

 
> sqlplus / as sysdba
 
SQL*Plus: Release 10.2.0.3.0 - Production on Fri Dec 12 08:31:08 2008
 
Copyright (c) 1982, 2006, Oracle.  All Rights Reserved.
 
 
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - Production
With the Partitioning, OLAP and Data Mining options
 
SQL> @find_sql
Enter value for sql_text: %skew%
Enter value for sql_id:
 
SQL_ID         CHILD  PLAN_HASH      EXECS     AVG_ETIME      AVG_LIO INVALIDATIONS    SQL_TEXT
------------- ------ ---------- ---------- ------------- ------------ ------------- -----------------------------------------------------
0gza16w5ka67q      0 1528838287          1           .01          249             0 SELECT   count(*)    FROM kso.skew where pk_col < 10
688rj6tv1bav0      0 3723858078          1          2.15       37,270             1 select avg(pk_col) from kso.skew where col1 = 1
688rj6tv1bav0      1  568322376          1          5.09      173,731             1 select avg(pk_col) from kso.skew where col1 = 1
7cbu7dgt0vh6y      0 1528838287          1           .00          226             0 select count(*) from kso.skew where pk_col < 10
 
SQL> @flush_sql_ol
 
Session altered.
 
Enter value for sql_id: 688rj6tv1bav0
Enter value for child_number: 1
 
SQL Statement 688rj6tv1bav0 flushed.
(Note also that outline SYS_OUTLINE_08121120170934217 was dropped.)
 
PL/SQL procedure successfully completed.
 
SQL> @find_sql
Enter value for sql_text: %skew%
Enter value for sql_id:
 
SQL_ID         CHILD  PLAN_HASH      EXECS     AVG_ETIME      AVG_LIO INVALIDATIONS    SQL_TEXT
------------- ------ ---------- ---------- ------------- ------------ ------------- -----------------------------------------------------
0gza16w5ka67q      0 1528838287          1           .01          249             0 SELECT   count(*)    FROM kso.skew where pk_col < 10
7cbu7dgt0vh6y      0 1528838287          1           .00          226             0 select count(*) from kso.skew where pk_col < 10

Statspack Still Works in 11g

Oracle 10g came with a new version of Statspack - they called it AWR, but basically it’s the same old estat/bstat report. There are of course a few new things in it and the snapshot process is automatically configured to collect data once per hour when you create a 10g instance. Unfortunately, this “new” feature is part of the separately licensed Diagnostics Pack, despite the fact the there is no easy way to disable the data collection. I posted earlier on Oracle Management Packs and associated licensing here, by the way.

But Statspack is still available and still runs fine if you choose to install it. As a matter of fact, it still exists in 11gR1 and has in fact been updated to accommodate some of the data dictionary changes in 11g. It looks a little like an after thought though as there are a couple of problems with it. The script to set it up is still in the $ORACLE_HOME/rdbms/admin directory and it’s still called spcreate.sql. This script creates the PERFSTAT user and the tables and necessary code objects.

The Statspack report looks very similar to the AWR report. It has basically the same Header Section, the same Wait Events Section, the same SQL Statements Ordered By XXX Sections, the same Tablespace and File I/O Sections, the same Buffer Pool Advisory Section, and the same Non-Default INIT.ORA Parameters Section at the bottom. Hey, this is the same thing!

The best things about Statspack:

  1. It is very, very similar to AWR
  2. It’s Open Source (sort of) - AWR is wrapped, but Statspack isn’t. You can modify it to suit your own taste. 
  3. It’s still free!

  Continue reading ‘Statspack Still Works in 11g’ »

Explain Plan Lies

The Oracle Explain Plan command is widely used to evaluate the plan that the Oracle optimizer will choose for a given SQL statement. Unfortunately, it doesn’t always tell the truth. This is due to the fact that the Explain Plan statement does not go through the same code path that the optimizer uses when determining a plan for execution. One of the simplest examples of this behavior is the case where bind variables are used in a statement. Explain plan ignores them while the optimizer uses them to determine the plan. Here’s an example.

SQL> @test_bind
SQL> var how_many number
SQL> accept col1 -
>        prompt 'Enter value for col1: '
SQL>
SQL> DECLARE
2  x number;
3  BEGIN
4
5  :how_many := 0;
6  x := &col1;
7
8  select avg(pk_col) into :how_many
9  from kso.skew
10  where col1 = x;
11
12  dbms_output.put_line(trunc(:how_many)||' records.');
13
14  END;
15  /
16487500 records.
 
PL/SQL procedure successfully completed.
 
SQL>
SQL> undef col1
SQL> undef how_many
SQL>
SQL> @find_sql
Enter value for sql_text: select avg(pk_col) from kso.skew where col1 =%
Enter value for sql_id:
 
SQL_ID         CHILD  PLAN_HASH      EXECS     AVG_ETIME      AVG_LIO SQL_TEXT
------------- ------ ---------- ---------- ------------- ------------ -----------------------------------------
7rf1jw8jsw0v7      0  568322376          1         48.32      173,734 SELECT AVG(PK_COL) FROM KSO.SKEW WHERE COL1 = :B1
 
SQL> -- Only 1 plan for this statement
SQL>
SQL>@dplan
Enter value for sql_id: 7rf1jw8jsw0v7
Enter value for child_no: 0
 
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  7rf1jw8jsw0v7, child number 0
-------------------------------------
SELECT AVG(PK_COL) FROM KSO.SKEW WHERE COL1 = :B1
 
Plan hash value: 568322376
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |       |       | 31720 (100)|          |
|   1 |  SORT AGGREGATE    |      |     1 |    11 |            |          |
|*  2 |   TABLE ACCESS FULL| SKEW |  3150K|    33M| 31720  (37)| 00:00:43 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
2 - filter("COL1"=:B1)
 
19 rows selected.
 
SQL> -- It did a full table scan expecting 3M rows
SQL>-- Now lets see what Explain Plan thinks it should have done
SQL>
SQL> explain plan for select avg(pk_col) from kso.skew where col1 = :b1;
 
Explained.
 
SQL> @xplan
 
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3723858078
 
------------------------------------------------------------------------------------------
| Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |           |     1 |    11 |    53   (2)| 00:00:01 |
|   1 |  SORT AGGREGATE              |           |     1 |    11 |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID| SKEW      |    53 |   583 |    53   (2)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN          | SKEW_COL1 |    54 |       |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------------------
 
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
 
1 - SEL$1
2 - SEL$1 / SKEW@SEL$1
3 - SEL$1 / SKEW@SEL$1
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
3 - access("COL1"=TO_NUMBER(:B1))
 
Column Projection Information (identified by operation id):
-----------------------------------------------------------
 
1 - (#keys=0) AVG("PK_COL")[22]
2 - "PK_COL"[NUMBER,22]
3 - "SKEW".ROWID[ROWID,10]
 
29 rows selected.
 
SQL> -- It thinks it will use an index (and that it will get only 54 rows - even though the histogram knows better
SQL>
SQL> select count(*) from kso.skew where col1=1;
 
COUNT(*)
----------
3199971

10g has a very nice utility (DBMS_XPLAN) to display plans from various locations (AWR, V$SQL_PLAN and Plan Tables). It has been expanded in 11g to work with Plan Baselines as well. So stop using Explain Plan and start looking at the real plan that the optimizer comes up with.

  • Just execute the statement (it’s Ok to hit control-C if it’s a long running statement, it will still be in the shared pool)
  • Find the SQL ID for the statement (I use find_sql.sql for this step)
  • Then use dbms_xplan.display_cursor to see the plan (I use dplan.sql for this this step).

There is are a couple of great posts by Rob Van Wijk and Jonathan Lewis on DBMS_XPLAN.DISPLAY_CURSOR on their blogs. Also the Oracle Optimizer Development Group Blog has a good post on the subject which includes 11g options.