Oracle FAQ Your Portal to the Oracle Knowledge Grid
HOME | ASK QUESTION | ADD INFO | SEARCH | E-MAIL US
 

Home -> Community -> Usenet -> c.d.o.server -> Re: Oracle Optmizer does not use the right index (Oracle 9.2.0.6)

Re: Oracle Optmizer does not use the right index (Oracle 9.2.0.6)

From: Charles Hooper <hooperc2000_at_yahoo.com>
Date: 15 Mar 2007 15:15:17 -0700
Message-ID: <1173996917.284865.321590@n59g2000hsh.googlegroups.com>


On Mar 15, 3:45 pm, alano..._at_invera.com wrote:
> Hello,
>
> Not sure whether this problem has been discussed earlier in this
> forum. Can someone suggest a workaround to the following problem which
> can be replicated by following the sequence of detailed steps I have
> given below. In summary, if you have a table with two composite
> indexes, both with the same fields, but one of them has a field in
> descending order, then the Optimizer loses track of the correct index
> to use for a specific query.
>
> Thanks.
>
> A.J.Andrews
>
> See details below:
> ----------------
>
> SQL*Plus: Release 9.2.0.6.0 - Production on Mon Feb 27 08:29:06 2006
>
> Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
>
> Connected to:
> Oracle9i Enterprise Edition Release 9.2.0.6.0 - 64bit Production
> With the Partitioning, OLAP and Oracle Data Mining options
> JServer Release 9.2.0.6.0 - Production
>
> SQL> create table tab1 as select * from all_objects;
>
> Table created.
>
> SQL> set autotrace traceonly explain;
> SQL> create index tab1ind1 on tab1(owner, object_name);
>
> Index created.
>
> SQL> analyze table tab1 compute statistics;
>
> Table analyzed.
>
> SQL> alter session set optimizer_mode=first_rows;
> Session altered.
>
> SQL> select * from tab1 order by owner, object_name;
>
> Execution Plan
> ----------------------------------------------------------
> 0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=172 Card=4899
> Bytes=372324)
>
> 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TAB1' (Cost=172
> Card=4899 Bytes=372324)
>
> 2 1 INDEX (FULL SCAN) OF 'TAB1IND1' (NON-UNIQUE) (Cost=46
> Card=4899)
>
> ===> Note here that the correct index tab1ind1 is being used for the
> query.
>
> SQL> create index tab1ind2 on tab1(owner, object_name desc);
>
> Index created.
>
> SQL> analyze table tab1 compute statistics;
>
> Table analyzed.
>
> SQL> select * from tab1 order by owner, object_name desc;
>
> Execution Plan
> ----------------------------------------------------------
> 0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=172 Card=4899
> Bytes=372324)
>
> 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TAB1' (Cost=172
> Card=4899 Bytes=372324)
>
> 2 1 INDEX (FULL SCAN) OF 'TAB1IND2' (NON-UNIQUE) (Cost=48
> Card=4899)
>
> ===> Again, the correct index tab1ind2 is being used.
>
> SQL> select * from tab1 order by owner, object_name;
>
> Execution Plan
> ----------------------------------------------------------
> 0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=288 Card=4899
> Bytes=372324)
>
> 1 0 SORT (ORDER BY) (Cost=288 Card=4899 Bytes=372324)
> 2 1 TABLE ACCESS (FULL) OF 'TAB1' (Cost=3 Card=4899
> Bytes=372324)
>
> ===> But, at this time the Optimizer has forgotten about the existence
> of tab1ind1 and is going for a full table scan and a sort! Wrong
> choice! Should be using index tab1ind1.
>
> SQL> drop index tab1ind2;
>
> Index dropped.
>
> SQL> select * from tab1 order by owner, object_name;
>
> Execution Plan
> ----------------------------------------------------------
> 0 SELECT STATEMENT Optimizer=FIRST_ROWS (Cost=172 Card=4899
> Bytes=372324)
>
> 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TAB1' (Cost=172
> Card=4899 Bytes=372324)
>
> 2 1 INDEX (FULL SCAN) OF 'TAB1IND1' (NON-UNIQUE) (Cost=46
> Card=4899)
>
> ===> Once tab1ind2 is dropped, the Optimizer remembers the existence
> of tab1ind1 and chooses the index scan for the very same query for
> which it used a table scan earlier.
>
> This problem is easily reproducible with any two indexes on a table,
> with both indexes being on the same set of columns, but with one of
> the indexes having a "desc" requirement on at least one of the columns.

FIRST_ROWS has a tendency to prefer index access, even if the index access that is selected is less than ideal. You should be using DBMS_STATS. With that said, a test on Oracle 10.2.0.2:

CREATE TABLE T2 AS
SELECT
  *
FROM
  ALL_OBJECTS; Table created.

COMMIT; Commit complete.

CREATE INDEX T2_IND1 ON T2(TABLE_OWNER_HERE, OBJECT_NAME); Index created.

EXEC
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'TABLE_OWNER_HERE',TABNAME=>'T2',CASCADE=>TRUE); PL/SQL procedure successfully completed.

ALTER SESSION SET OPTIMIZER_MODE=FIRST_ROWS; Session altered.

SELECT
  *
FROM
  T2
ORDER BY
  OWNER,
  OBJECT_NAME; SELECT
  *
FROM
  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS')); SQL_ID 9jf1t5g0sgzz4, child number 0



SELECT /*+ FIRST_ROWS */ * FROM T2 ORDER BY OWNER, OBJECT_NAME Plan hash value: 1199450076
| Id  | Operation                   | Name    | Starts | E-Rows | A-
Rows | A-Time | Buffers | Reads |
|   1 |  TABLE ACCESS BY INDEX ROWID| T2      |      2 |  13945 |
27890 |00:00:01.12 |   12286 |    210 |
|   2 |   INDEX FULL SCAN           | T2_IND1 |      2 |  13945 |
27890 |00:00:00.55 |     410 |    136 |
----------------------------------------------------------------------------------------------------------

CREATE INDEX T2_IND2 ON T2(TABLE_OWNER_HERE, OBJECT_NAME DESC); Index created.

EXEC
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>'TABLE_OWNER_HERE',TABNAME=>'T2',CASCADE=>TRUE); PL/SQL procedure successfully completed.

SELECT
  *
FROM
  T2
ORDER BY
  OWNER,
  OBJECT_NAME DESC; SELECT
  *
FROM
  TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL,NULL,'ALLSTATS'));


| Id  | Operation                   | Name    | E-Rows |
--------------------------------------------------------
|   1 |  TABLE ACCESS BY INDEX ROWID| T2      |  13945 |
|   2 |   INDEX FULL SCAN           | T2_IND2 |  13945 |

SELECT
  *
FROM
  T2
ORDER BY
  OWNER,
  OBJECT_NAME; SQL_ID 9jf1t5g0sgzz4, child number 0



SELECT /*+ FIRST_ROWS */ * FROM T2 ORDER BY OWNER, OBJECT_NAME Plan hash value: 2552596561

| Id | Operation | Name | Starts | E-Rows | A-Rows | A- Time | Buffers | Reads | OMem | 1Mem | O/1/M |
|   1 |  SORT ORDER BY     |      |      1 |  13945 |  13945 |
00:00:00.15 |     182 |     37 |  1824K|   650K|     1/0/0|
|   2 |   TABLE ACCESS FULL| T2   |      1 |  13945 |  13945 |
00:00:00.11 |     182 |     37 |       |       |          |
-------------------------------------------------------------------------------------------------------------------------
So, we are consistent so far.

ALTER SYSTEM FLUSH SHARED_POOL; DROP INDEX T2_IND2; Let's try a test:
SELECT /*+ ALL_ROWS */
  *
FROM
  T2
ORDER BY
  OWNER,
  OBJECT_NAME; SQL_ID b3myw3j5ufp1h, child number 0



SELECT /*+ ALL_ROWS */ * FROM T2 ORDER BY OWNER, OBJECT_NAME Plan hash value: 2552596561

| Id | Operation | Name | Starts | E-Rows | A-Rows | A- Time | Buffers | Reads | OMem | 1Mem | O/1/M |
|   1 |  SORT ORDER BY     |      |      1 |  13945 |  13945 |
00:00:00.25 |     182 |     37 |  1824K|   650K|     1/0/0|
|   2 |   TABLE ACCESS FULL| T2   |      1 |  13945 |  13945 |
00:00:00.21 |     182 |     37 |       |       |          |
-------------------------------------------------------------------------------------------------------------------------

That plan and statistics look to be very similar to the one that we received when both indexes were available to be used by the cost based optimizer. Note how the execution time is 0.25 seconds (or 0.15 seconds when there were two indexes) compared to the 1.12 seconds required when the T2_IND1 index was used. Is it possible that the cost based optimizer recognized that the time required to continue analyzing the various access methods would exceed the time required to return the rows, so the optimizer decided to run with the best plan that it found so far?

Let's look at a 10053 trace for the case when there were two indexes available:
BASE STATISTICAL INFORMATION



Table Stats::
  Table: T2 Alias: T2
    #Rows: 13945 #Blks: 190 AvgRowLen: 105.00 Index Stats::
  Index: T2_IND1 Col#: 1 2
    LVLS: 1 #LB: 67 #DK: 12870 LB/K: 1.00 DB/K: 1.00 CLUF: 5859.00
  Index: T2_IND2 Col#: 1 14
    LVLS: 1 #LB: 69 #DK: 12870 LB/K: 1.00 DB/K: 1.00 CLUF: 5870.00

SINGLE TABLE ACCESS PATH
  Table: T2 Alias: T2
    Card: Original: 13945 Rounded: 13945 Computed: 13945.00 Non Adjusted: 13945.00
  Access Path: TableScan
    Cost: 38.54 Resp: 38.54 Degree: 0
      Cost_io: 38.00  Cost_cpu: 6791624
      Resp_io: 38.00  Resp_cpu: 6791624
  Best:: AccessPath: TableScan
         Cost: 38.54  Degree: 1  Resp: 38.54  Card: 13945.00  Bytes: 0
***************************************

OPTIMIZER STATISTICS AND COMPUTATIONS

GENERAL PLANS

Considering cardinality-based initial join order. Permutations for Starting Table :0

Join order[1]: T2[T2]#0
ORDER BY sort
    SORT resource      Sort statistics
      Sort width:         238 Area size:      208896 Max Area size:
41943040
      Degree:               1
      Blocks to Sort:     187 Row size:          109 Total
Rows:          13945
      Initial runs:         2 Merge passes:        1 IO Cost /
pass:        102
      Total IO sort cost: 289      Total CPU sort cost: 25948637
      Total Temp space used: 3384000
***********************

Best so far: Table#: 0 cost: 329.5793 card: 13945.0000 bytes: 1255050
(newjo-stop-1) k:0, spcnt:0, perm:1, maxperm:80000

Number of join permutations tried: 1
    SORT resource      Sort statistics
      Sort width:         238 Area size:      208896 Max Area size:
41943040
      Degree:               1
      Blocks to Sort:     187 Row size:          109 Total
Rows:          13945
      Initial runs:         2 Merge passes:        1 IO Cost /
pass:        102
      Total IO sort cost: 289      Total CPU sort cost: 25948637
      Total Temp space used: 3384000

Final - First Rows Plan: Best join order: 1
  Cost: 329.5793  Degree: 1  Card: 13945.0000  Bytes: 1255050
  Resc: 329.5793  Resc_io: 327.0000  Resc_cpu: 32740260
  Resp: 329.5793  Resp_io: 327.0000  Resc_cpu: 32740260

The total expected cost was 329 - 289 of which was caused by the sort.

Now, let's look at the case where only the first index exists (FIRST_ROWS):
BASE STATISTICAL INFORMATION



Table Stats::
  Table: T2 Alias: T2
    #Rows: 13945 #Blks: 190 AvgRowLen: 105.00 Index Stats::
  Index: T2_IND1 Col#: 1 2
    LVLS: 1 #LB: 67 #DK: 12870 LB/K: 1.00 DB/K: 1.00 CLUF: 5859.00

SINGLE TABLE ACCESS PATH
  Table: T2 Alias: T2
    Card: Original: 13945 Rounded: 13945 Computed: 13945.00 Non Adjusted: 13945.00
  Access Path: TableScan
    Cost: 38.54 Resp: 38.54 Degree: 0
      Cost_io: 38.00  Cost_cpu: 6791624
      Resp_io: 38.00  Resp_cpu: 6791624
  Best:: AccessPath: TableScan
         Cost: 38.54  Degree: 1  Resp: 38.54  Card: 13945.00  Bytes: 0
***************************************

OPTIMIZER STATISTICS AND COMPUTATIONS

GENERAL PLANS

Considering cardinality-based initial join order. Permutations for Starting Table :0

Join order[1]: T2[T2]#0
ORDER BY sort
    SORT resource      Sort statistics
      Sort width:         238 Area size:      208896 Max Area size:
41943040
      Degree:               1
      Blocks to Sort:     187 Row size:          109 Total
Rows:          13945
      Initial runs:         2 Merge passes:        1 IO Cost /
pass:        102
      Total IO sort cost: 289      Total CPU sort cost: 25948637
      Total Temp space used: 3384000
***********************

Best so far: Table#: 0 cost: 329.5793 card: 13945.0000 bytes: 1255050
****** Recost for ORDER BY (using index) ************

SINGLE TABLE ACCESS PATH
  Table: T2 Alias: T2
    Card: Original: 13945 Rounded: 13945 Computed: 13945.00 Non Adjusted: 13945.00
  Access Path: TableScan
    Cost: 38.54 Resp: 38.54 Degree: 0
      Cost_io: 38.00  Cost_cpu: 6791624
      Resp_io: 38.00  Resp_cpu: 6791624
  Access Path: index (FullScan)

    Index: T2_IND1
    resc_io: 5927.00 resc_cpu: 50436325     ix_sel: 1 ix_sel_with_filters: 1
    Cost: 5930.97 Resp: 5930.97 Degree: 1   Best:: AccessPath: IndexRange Index: T2_IND1

         Cost: 5930.97 Degree: 1 Resp: 5930.97 Card: 13945.00 Bytes: 90



Join order[1]: T2[T2]#0

Best so far: Table#: 0 cost: 5930.9733 card: 13945.0000 bytes: 1255050
(newjo-stop-1) k:0, spcnt:0, perm:1, maxperm:80000

Number of join permutations tried: 1
    SORT resource      Sort statistics
      Sort width:         238 Area size:      208896 Max Area size:
41943040
      Degree:               1
      Blocks to Sort:     187 Row size:          109 Total
Rows:          13945
      Initial runs:         2 Merge passes:        1 IO Cost /
pass:        102
      Total IO sort cost: 289      Total CPU sort cost: 25948637
      Total Temp space used: 3384000

Final - First Rows Plan: Best join order: 1
  Cost: 5930.9733  Degree: 1  Card: 13945.0000  Bytes: 1255050
  Resc: 5930.9733  Resc_io: 5927.0000  Resc_cpu: 50436325
  Resp: 5930.9733  Resp_io: 5927.0000  Resc_cpu: 50436325

The cost in this case is 5930, which is a bit higher than the cost of 329 that was predicted for the table access with sort operation. It appeared that when there were two indexes on the table, Oracle did not even bother checking the index access paths, as it expected the time to calculate the best path would be more than the time to return the data.

Paraphrased from "Cost-Based Oracle Fundamentals" (very likely the best book to read and re-read when trying to determine what is happening): "The optimizer may decide after testing a few join orders that the cost of executing the query is so small that it should discontinue checking join orders - as soon as the time spent checking join orders exceeds the predicted run time, Oracle runs with the best plan obtained so far. This means that if Oracle happens to pick an unsuitable starting table because it calculated the cardinality wrong, the standard permutation cycle of joining tables can cause problems."

It does not look like a bug to me, but instead an optimization.

Charles Hooper
PC Support Specialist
K&M Machine-Fabricating, Inc. Received on Thu Mar 15 2007 - 17:15:17 CDT

Original text of this message

HOME | ASK QUESTION | ADD INFO | SEARCH | E-MAIL US