The orderly arrangement of the index keys in the leaf blocks makes it possible for Oracle to traverse the index both backward and forward. It renders, as such, the CBO in the plausible capacity of eliminating a costly order by operation. This order by elimination is, however, possible provided the index is used and the query order is wanted in the exact direction of the index columns backward or forward.
Let’s clarify this with a simple example
The model
create table t1 as select rownum n1 ,trunc((rownum-1)/3) n2 ,mod(rownum,5) n3 ,lpad('x',5) v1 from dual connect by level <=1e3; create index t1_idx on t1(n1, n2, n3); explain plan for select * from t1 where n1 <=5 order by n1,n2, n3; select * from table(dbms_xplan.display); ------------------------------------------------------ | Id | Operation | Name | Rows | ------------------------------------------------------ | 0 | SELECT STATEMENT | | 5 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 5 | |* 2 | INDEX RANGE SCAN | T1_IDX | 5 | ------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("N1"<=5)
You should note in the above expected execution plan the absence of a SORT ORDER BY operation. This has been possible because the order in which the query result set is wanted matches perfectly the order in which the columns have been arranged in the index. Oracle is also able to avoid a SORT operation if the order of the query result set is wanted in the exact reverse direction of the index columns as shown below:
explain plan for select * from t1 where n1 <=5 order by n1 desc,n2 desc, n3 desc; select * from table(dbms_xplan.display); ------------------------------------------------------- | Id | Operation | Name | Rows | ------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | | 1 | TABLE ACCESS BY INDEX ROWID | T1 | 5 | |* 2 | INDEX RANGE SCAN DESCENDING| T1_IDX | 5 | ------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("N1"<=5)
Again the SORT ORDER BY operation has been avoided thanks to the ability of Oracle to traverse the index in a descending order (backward). However, if the result set is wanted in an order that is neither in the index direction nor in its opposite one, then it will be impossible for Oracle to avoid a SORT operation even when the index is used as the following demonstrates:
explain plan for select * from t1 where n1 <=5 order by n1 ,n2 desc, n3 ; select * from table(dbms_xplan.display); --------------------------------------------------------------- | Id | Operation | Name | Rows | --------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | | 1 | SORT ORDER BY | | 5 | | 2 | TABLE ACCESS BY INDEX ROWID BATCHED| T1 | 5 | |* 3 | INDEX RANGE SCAN | T1_IDX | 5 | --------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 3 - access("N1"<=5)
As you can see, this time, in contrast to the first two cases, a SORT ORDER BY operation kicks in. This operation becomes mandatory since the result set has been desired in an order that doesn’t match the order of the index columns. Spot in passing that when Oracle avoids a SORT ORDER BY it doesn’t visit the T1 table in a BATCHED mode and vice versa. I have already wrote a note about this here.
If you are in a situation where you want absolutely to avoid a costly SORT ORDER BY operation then you need to create a new matching index like the following one:
alter index t1_idx invisible; create index t1_idx2 on t1(n1,n2 desc, n3); explain plan for select * from t1 where n1 <=5 order by n1 ,n2 desc, n3 ; select * from table(dbms_xplan.display); --------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | --------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 85 | | 1 | TABLE ACCESS BY INDEX ROWID| T1 | 5 | 85 | |* 2 | INDEX RANGE SCAN | T1_IDX2 | 5 | | --------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("N1"<=5)
The same desired order could also be achieved without a SORT ORDER BY operation by creating the following index:
drop index t1_idx2; create index t1_idx2 on t1(n1 desc, n2, n3 desc); explain plan for select * from t1 where n1 <=5 order by n1 ,n2 desc, n3 ; select * from table(dbms_xplan.display); -------------------------------------------------------- | Id | Operation | Name | Rows | -------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | | 1 | TABLE ACCESS BY INDEX ROWID | T1 | 5 | |* 2 | INDEX RANGE SCAN DESCENDING| T1_IDX2 | 9 | -------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access(SYS_OP_DESCEND("N1")>=HEXTORAW('3EF9FF') AND SYS_OP_DESCEND("N1") IS NOT NULL) filter(SYS_OP_UNDESCEND(SYS_OP_DESCEND("N1"))<=5)
Looking at the above exotic predicate I am not sure that this last case will always outperform the preceding one.
So What?
Since you know roughly how an index should be designed to make a sort operation avoidable, are you going to change the ORDER BY clause to suit an existing index or create a new index to match the ORDER BY clause? The answer to this question has been magnificently given by Peter Gulutzan and Trudy Pelzer in their SQL Performance Tuning book “In both cases, we would have to say No. If you did either one, you’d be crossing the boundary between “taking advantage of a side effect” and “depending on a side effect.”
Because indexes exist mainly to support WHERE clause rather than ORDER BY I would very probably suggest to not change the existing index. Particularly when real life experience confirms that it is easier to add an index than to drop an existing one. But when we know how a well designed index can precisely cover a query and eventually avoid a sort operation, we can either challenge the client for the order in which he wants his result set or, all things being equal, design an index to match the order by clause.
Summary
Watch out your critical queries necessitating a certain order. If you are going to design an index covering one of their predicates, and all things being equal, think about designing this index so that its columns will match the query order by clause either backward or forward. But you shouldn’t being always forcing the CBO to use an index merely because it will avoid a SORT operation.
PS: this post has been inspired by an excellent blog on PostgreSQL by Bruce Momjian