Your date column has an index. Oracle still does a full table scan.

I wanted to search a year of audit history for one rare value. The table has an index on the timestamp, so I assumed a date filter would make the query cheap. The optimizer disagreed, and it was right.

Check the plan at a few window sizes instead of guessing:

explain plan for
select count(*) from audit_tbl a
 where sys_extract_utc(a.a_date_) >= sys_extract_utc(systimestamp) - interval '30' day(3)
   and a.some_col in ('...');

select * from table(dbms_xplan.display(null, null, 'BASIC +COST +ROWS'));

On a 6.2M-row, 523k-block table the results were:

  • 7 days: index range scan, cost ~96K
  • 30 days: full table scan, cost ~234K
  • 90 days and 1 year: full table scan, cost ~234K

The two access paths scale differently. A full scan reads every block sequentially, many blocks per I/O, so its cost is the same whatever the window. An index range scan is cheap at the index itself. After that it fetches each matching row's table block one at a time, so its cost grows with the number of rows in the window.

How expensive those fetches are depends on the index's clustering factor:

select index_name, clustering_factor, num_rows from all_indexes where table_name = 'AUDIT_TBL';

A value close to the table's block count means rows with nearby keys sit together, so one block read serves many rows. A value close to the row count means nearly every row needs its own block read. Mine was 3.35M against 523k blocks, which is badly scattered. The break-even point landed somewhere between 7 and 30 days. Past that, forcing the index with a hint makes the query slower, not faster.

One related trap: if the index is function-based, for example on SYS_EXTRACT_UTC(a_date_), the predicate has to use the same expression. A plain a_date_ >= ... can't use that index at any window size.

Comments

  1. Markdown is allowed. HTML tags allowed: <strong>, <em>, <blockquote>, <code>, <pre>, <a>.