Your test is badly formed because ...
- the optimizer doesn't know that the "id" column is not null.
- placing the object_type second in the list of columns in the composite index reduces the chances of it being used
- The table is so small that the optimizer might choose to use an FTS anyway
- Since all the values of object_type are null, it's more efficient with this data set to FTS anyway
Code:
SQL> create table my_table as select * from dba_objects;
Table created.
SQL> update my_table set object_type = null
2 where rownum < 11;
10 rows updated.
SQL> desc my_table
Name Null? Type
----------------------------------------- -------- --------------------------
OWNER VARCHAR2(30)
OBJECT_NAME VARCHAR2(128)
SUBOBJECT_NAME VARCHAR2(30)
OBJECT_ID NUMBER
DATA_OBJECT_ID NUMBER
OBJECT_TYPE VARCHAR2(18)
CREATED DATE
LAST_DDL_TIME DATE
TIMESTAMP VARCHAR2(19)
STATUS VARCHAR2(7)
TEMPORARY VARCHAR2(1)
GENERATED VARCHAR2(1)
SECONDARY VARCHAR2(1)
SQL> alter table my_table modify (owner not null);
Table altered.
SQL> create index my_index on my_table (object_type,owner);
Index created.
SQL> analyze table my_table compute statistics;
Table analyzed.
SQL> set autotrace traceonly explain
SQL> select * from my_table where object_type is null;
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=3 Card=10 Bytes=860)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'MY_TABLE' (Cost=3 Card=1
0 Bytes=860)
2 1 INDEX (RANGE SCAN) OF 'MY_INDEX' (NON-UNIQUE) (Cost=2 Ca
rd=10)