it is because of the stats (or rather, the LACK of stats :) )
When I ran it - your dbms_stats.gather_stats commands failed due to your word wrapping (the exec had a line feed after it). So, in all cases, we used dynamic sampling. Without stats gathered, you would see:
ops$tkyte%ORA11GR2> -- counting a=2:
ops$tkyte%ORA11GR2> select /*+ gather_plan_statistics */ count(*) from test_index_count6 where a=2;
COUNT(*)
----------
2
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 9pqb4xj77s7zx, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from test_index_count6
where a=2
Plan hash value: 1917981987
------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 3 |
|* 2 | INDEX RANGE SCAN| IND_A_TIC6 | 1 | 2 | 2 |00:00:00.01 | 3 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("A"=2)
Note
-----
- dynamic sampling used for this statement (level=2)
24 rows selected.
ops$tkyte%ORA11GR2> -- counting a=1:
ops$tkyte%ORA11GR2> select /*+ gather_plan_statistics */ count(*) from test_index_count6 where a=1;
COUNT(*)
----------
1000000
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 33uf1whr4a6dy, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from test_index_count6
where a=1
Plan hash value: 3219185018
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.13 | 1591 | 579 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.13 | 1591 | 579 |
|* 2 | TABLE ACCESS FULL| TEST_INDEX_COUNT6 | 1 | 836K| 1000K|00:00:00.53 | 1591 | 579 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"=1)
Note
-----
- dynamic sampling used for this statement (level=2)
24 rows selected.
which is exactly what you expect (i assume). However, when I "fix" your script, I see:
ops$tkyte%ORA11GR2> -- counting a=2:
ops$tkyte%ORA11GR2> select /*+ gather_plan_statistics */ count(*) from test_index_count6 where a=2;
COUNT(*)
----------
2
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 9pqb4xj77s7zx, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from test_index_count6
where a=2
Plan hash value: 3219185018
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.04 | 1588 | 1582 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.04 | 1588 | 1582 |
|* 2 | TABLE ACCESS FULL| TEST_INDEX_COUNT6 | 1 | 500K| 2 |00:00:00.04 | 1588 | 1582 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"=2)
20 rows selected.
ops$tkyte%ORA11GR2> -- counting a=1:
ops$tkyte%ORA11GR2> select /*+ gather_plan_statistics */ count(*) from test_index_count6 where a=1;
COUNT(*)
----------
1000000
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 33uf1whr4a6dy, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from test_index_count6
where a=1
Plan hash value: 3219185018
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.11 | 1588 | 1582 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.11 | 1588 | 1582 |
|* 2 | TABLE ACCESS FULL| TEST_INDEX_COUNT6 | 1 | 500K| 1000K|00:00:00.53 | 1588 | 1582 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"=1)
20 rows selected.
Now, why does it do that with stats gathered?
Because by default, the method-opt => auto and auto will not gather histograms unless it has some reason to believe it needs to.
If we did this *right after* you ran your testcase:
ops$tkyte%ORA11GR2> exec dbms_stats.gather_table_stats(user,estimate_percent => 100, method_opt=> 'for columns a size 254', tabname=>'TEST_INDEX_COUNT6' );
PL/SQL procedure successfully completed.
ops$tkyte%ORA11GR2> -- selecting a=2:
ops$tkyte%ORA11GR2> select /*+ gather_plan_statistics */ * from test_index_count6 ensure_hard_parsex where a=2;
A
----------
2
2
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 4278v0zpuwuq3, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ * from test_index_count6
ensure_hard_parsex where a=2
Plan hash value: 1048529912
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 2 |00:00:00.01 | 4 | 1 |
|* 1 | INDEX RANGE SCAN| IND_A_TIC6 | 1 | 2 | 2 |00:00:00.01 | 4 | 1 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("A"=2)
19 rows selected.
ops$tkyte%ORA11GR2> -- counting a=2:
ops$tkyte%ORA11GR2> select /*+ gather_plan_statistics */ count(*) from test_index_count6 ensure_hard_parsex where a=2;
COUNT(*)
----------
2
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID baz0t5tfxpnxs, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from test_index_count6
ensure_hard_parsex where a=2
Plan hash value: 1917981987
------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.01 | 3 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.01 | 3 |
|* 2 | INDEX RANGE SCAN| IND_A_TIC6 | 1 | 2 | 2 |00:00:00.01 | 3 |
------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("A"=2)
20 rows selected.
ops$tkyte%ORA11GR2> -- counting a=1:
ops$tkyte%ORA11GR2> select /*+ gather_plan_statistics */ count(*) from test_index_count6 ensure_hard_parsex where a=1;
COUNT(*)
----------
1000000
ops$tkyte%ORA11GR2> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'));
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID c2v5b5tbbup5s, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from test_index_count6
ensure_hard_parsex where a=1
Plan hash value: 3219185018
-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 1 |00:00:00.12 | 1588 | 1582 |
| 1 | SORT AGGREGATE | | 1 | 1 | 1 |00:00:00.12 | 1588 | 1582 |
|* 2 | TABLE ACCESS FULL| TEST_INDEX_COUNT6 | 1 | 1000K| 1000K|00:00:00.63 | 1588 | 1582 |
-----------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("A"=1)
20 rows selected.
you'd get the 'right' statistics. In fact, you probably don't even need the method_opt, just the running of the queries should be enough (but might not be in this truly massively skewed example - we might not sample 2 at all - it might not get picked up)
See also
http://www.oracle.com/technetwork/issue-archive/2006/06-may/o36asktom-099107.html "Why Does My Plan Change?"