views:

365

answers:

5

I have a partitioned table like so:

create table demo (
    ID NUMBER(22) not null,
    TS TIMESTAMP not null,
    KEY VARCHAR2(5) not null,
    ...lots more columns...
)

The partition is on the TS column (one partition per year).

Since I search a lot via the timestamp, I created a combined index:

create index demo.x1 on demo (ts, key);

The query looks like this:

select *
from demo t
where t.TS = to_timestamp('2009-06-30 07:47:57', 'YYYY-MM-DD HH24:MI:SS')

I also tried to add and t.KEY = '00101' but that doesn't help.

But EXPLAIN PLAN says that TABLE ACCESS and FULL:

#  Operation         Options Object Mode           Cost    Bytes   Cardinality
0  SELECT STATEMENT                ALL_ROWS        583804  287145  2127
1  PARTITION RANGE   ALL                           583804  287145  2127
2  TABLE ACCESS      FULL  HEADER  ANALYZED        583804  287145  2127

No mention of the index. What could be wrong?

[EDIT] For some reason, Oracle completely miscalculated the cost for the operation. I have 112 million rows in that table. The cost for a full scan of a single partition should be 20 million, not 600'000. That's why it even ignores optimizer hints.

[EDIT2] During my tests, I ran over this puzzling result. When I run this select:

select tx_ts
from kt.header
where tx_ts = to_timestamp('2009-06-30 07:47:57', 'YYYY-MM-DD HH24:MI:SS')

I get this EXPLAIN PLAN:

0  SELECT STATEMENT                             ALL_ROWS  152  15616  1952
1  PARTITION RANGE    ALL                                 152  15616  1952
2  INDEX              FAST FULL SCAN  HEADERX2  ANALYZED  152  15616  1952

So when I restrict myself to the indexed column as the result of the select, Oracle decides to use the index. When I want to get all columns, I have to wait for a full table scan. What's going on here?

[EDIT2] Found it; see my answer below.

+1  A: 

Are your stats up to date? Invalid stats may mean that oracle believes a full table scan is faster than using the index. Are you using any hints in your query that might be telling oracle to do a full scan?

Can you supply us with the full query and explain plan results?

Edit: Aaron, you can update the stats using "dbms_stats.gather_schema_stats" or "dbms_stats.gather_table_stats" commands. See here for more information on the commands. This will update all the relevant stats for the schema or table specified. Oracle's Cost Based Optimiser will use the statistics to determine which execution plan to choose. It never uses the actual table sizes. You'll need to re-update your stats when the size of your table changes significantly ( +/- 10% or so)

Another thing. When you use a compound index, you need to specify all the columns used in the index in your query for the optimizer to consider the index (and I think you need to specify them in the same order as well, though I could be wrong about that, it's been a while since I looked at this stuff)

Glen
I've edited my question. The index should be up to date since I just dropped and recreated it. How do I check the stats?
Aaron Digulla
+1  A: 

There may just be a typo in your transcription of the "CREATE INDEX..." statement that you posted, but are you sure you actually have created the index?

To give us some first-pass idea of the statistics, use these queries:

select table_name, num_rows
  from user_tables
  where table_name = 'DEMO';

 select table_name, num_rows
   from user_tab_partitions
   where table_name = 'DEMO';

 select index_name, num_rows from user_indexes                    
  where table_name in                   
    (select table_name                                   
      from user_tables where table_name = 'DEMO');

Also, exactly how are you generating the EXPLAIN PLAN? Do you have access to the database host to retrieve a trace file if you enable tracing?

[edit] As I commented, it would be good to see the trace of an actual execution of the query. Since you've indicated you have access to the db host filesystems, run a SQL script that (in the same session) issues the following:

alter session set sql_trace=true;
select /* THIS IS THE TRACE */  
*
from demo t
where t.TS = to_timestamp('2009-06-30 07:47:57', 'YYYY-MM-DD HH24:MI:SS');
exit
  • After the script exits, find out where the trace file directory is by this query:

    select value from v$parameter where name = 'user_dump_dest';

  • Use whatever searching tool is available to you to find the file that includes the string "THIS IS THE TRACE"

  • Process/profile the trace file by issuing the OS command tkprof traceFileName.trc tkprof.out

Examine this file - you'll see some overhead information, but there will be a section that details the actual execution plan and statistics for the query. If you see the same results in this information then the next step is to add another statement (after the first "alter session") that will dump information on why the Oracle CBO is ignoring the index:

ALTER SESSION SET EVENTS='10053 trace name context forever, level 1';
dpbradley
The first query returns 107,640,377, the second splits that number over five partitions. The last one return 112,153,440 for the index in question.
Aaron Digulla
One thing that might have an influence: I have created more partitions than I need (I've already created partitions for the next twenty years). Those are empty so far (so I have five partitions with roughly the same number of rows and twenty with 0 rows).
Aaron Digulla
That's good news - it looks like you're OK with the basic statistics then. As I mentioned, I think it might be good to see a traced execution - can you access the database host filesystem?
dpbradley
Oops, just saw your second comment after I posted my reply - I don't think the extra partitions would matter in this case.
dpbradley
Yes, I have access to the filesystem. What should I do?
Aaron Digulla
I'm adding this info in an [edit] section to my answer
dpbradley
*sigh* ORA-01031: insufficient privileges ... I'll be back after talking to the DBA.
Aaron Digulla
+1  A: 

I'm not really an expert on partitioning, but I think what has happened here is that you have created a global index -- a single index that covers rows in all of the partitions. Therefore, the optimizer has to choose between two mutually exclusive access paths: (A) an index range scan, or (B) partition pruning. I believe the PARTITION RANGE operation indicates that it has chosen B.

Updating statistics, as others have suggested, may change the behavior. When you drop and recreate the index, you discard any statistics that existed for the index.

Creating the index as UNIQUE, if the timestamp and key uniquely identify a row, would be a good idea and might change the behavior as well.

However, I think the real "fix" is that you should instead create local indexes -- a separate index on each partition. This should enable the optimizer to do partition pruning followed by an index lookup. Honestly, I'm not sure what the exact syntax is to do this. Maybe you just create the index on each partition explicitly using the individual partition names.

Dave Costa
For those who want to know more about this, look at http://download.oracle.com/docs/cd/B10501_01/server.920/a96524/c12parti.htm#459801. For an example how to create a local partitioned index, search for "Example of a Local Index Creation".
Aaron Digulla
Basically, you add the word "local" after the column list: `create index demo.x1 on demo (ts, key) LOCAL`
Aaron Digulla
I've now recreated the index but the result of the EXPLAIN PLAN doesn't change. I checked the contents of `USER_INDEXES` and the new index is there with `YES` in the column `PARTITIONED`. This is starting to irritate me :)
Aaron Digulla
Okay, after the tip of ammoQ, I tried a where with both index columns and now, it works! So my fix is now to use two indexes.
Aaron Digulla
Sorry, have to take that back. If I create an index just on TS and I just mention that column in the WHERE, Oracle does a full scan :(
Aaron Digulla
+1  A: 

If everything else fails, you might try an optimizer hint:

select /*+ index(demo.demo demo.x1) */ *
from demo t
where t.TS = to_timestamp('2009-06-30 07:47:57', 'YYYY-MM-DD HH24:MI:SS')
ammoQ
Thanks for the tip. Oracle can't be bothered :( It stubbornly insists on a full table scan.
Aaron Digulla
But when I add the second index column (key), it suddenly works!
Aaron Digulla
A: 

Okay, it was a mistake on my part: The column had the type DATE, not TIMESTAMP. Since I used to_timestamp(), Oracle saw no way to use the index.

Aaron Digulla