You are on page 1of 7

Optimizer_mode ALL_ROWS or FIRST_ROWS?

Out of all Oracle RDBMS modules, optimizer code is actually the most complicated code and different optimizer modes seem like jack while lifting your car in case of a puncture. This paper focuses on how optimizer behaves differently when you have optimizer mode set to ALL_ROWS or FIRST_ROWS. Possible values for optimizer_mode = choose/ all_rows/ first_rows/ first_rows[n] By default, the value of optimizer_mode is CHOOSE which basically means ALL_ROWS (if statistics on underlying tables exist) else RULE (if there are no statistics on underlying tables). So it is very important to have statistics collected on your tables on regular intervals or else you are living in Stone Age. FIRST_ROWS and ALL_ROWS are both cost based optimizer features. You may use them according to their requirement. FIRST_ROWS/ FIRST_ROWS[n] In simple terms it ensures best response time of first few rows (n rows). This mode is good for interactive client-server environment where server serves first few rows and by the time user scroll down for more rows, it fetches other. So user feels that he has been served the data he requested, but in reality the request is still pending and query is still fetching the data in background. Best example for this is toad, if you click on data tab, it instantaneously start showing you data and you feel toad is faster than sqlplus, but the fact is if you scroll down, you will see the query is still running. Ok, let us simulate this on SQLPLUS Create a table and index over it: SQL> create table test as select * from all_objects; Table created. SQL> create index test_in on test(object_type); Index created. SQL> exec dbms_stats.gather_table_stats(SAC,'TEST') PL/SQL procedure successfully completed. SQL> select count(*) from test; COUNT(*) ---------37944

SQL> select count(*) from test where object_type='JAVA CLASS'; COUNT(*) ---------14927 You see out of almost 38k records, 15k are of JAVA class. And now if you select the rows having object_type=JAVA_CLASS, it should not use index as almost half of the rows are JAVA_CLASS. It will be foolish of optimizer to read the index first and then go to table. Check out the Explain plans

SQL> set autotrace traceonly exp SQL> select * from test where object_type='JAVA CLASS'; Execution Plan ---------------------------------------------------------Plan hash value: 1357081020 -------------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 1001 | 94094 | 10 (0)| 00:00:01 | |* 1 | TABLE ACCESS FULL| TEST | 1001 | 94094 | 10 (0)| 00:00:01 | -------------------------------------------------------------------------As you see above, optimizer has not used Index we created on this table. Now use FIRST_ROWS hint: SQL> select /*+ FIRST_ROWS*/ * from test where object_type='JAVA CLASS'; Execution Plan ---------------------------------------------------------Plan hash value: 3548301374 --------------------------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 14662 | 1345K| 536 (1)| 00:00:07 | | 1 | TABLE ACCESS BY INDEX ROWID| TEST | 14662 | 1345K| 536 (1)| 00:00:07 | |* 2 | INDEX RANGE SCAN | TEST_IN | 14662 | | 43 (3)| 00:00:01 | --------------------------------------------------------------------------------------In this case, optimizer has used the index. Q> Why? Ans> Because you wanted to see first few rows quickly. So, following your instructions oracle delivered you first few rows quickly using index and later delivering the rest. See the difference in cost, although the response time (partial) of second query was faster but resource consumption was high. But that does not mean that this optimizer mode is bad. As I said this mode may be good for interactive client-server model. In most of OLTP systems, where users want to see data fast on their

screen, this mode of optimizer is very handy. Important facts about FIRST_ROWS 1. It gives preference to Index scan Vs Full scan (even when index scan is not good). 2. It prefers nested loop over hash joins because nested loop returns data as selected (& compared), but hash join hashes one first input in hash table which takes time. 3. Cost of the query is not the only criteria for choosing the execution plan. It chooses plan which helps in fetching first rows fast. 4. It may be a good option to use this in an OLTP environment where user wants to see data as early as possible. ALL_ROWS In simple terms, it means better throughput While FIRST_ROWS may be good in returning first few rows, ALL_ROWS ensures the optimum resource consumption and throughput of the query. In other words, ALL_ROWS is better to retrieve the last row first. In above example while explaining FIRST_ROWS, you have already seen how efficient ALL_ROWS is. Important facts about ALL_ROWS 1. ALL_ROWS considers both index scan and full scan and based on their contribution to the overall query, it uses them. If Selectivity of a column is low, optimizer may use index to fetch the data (for example where employee_code=7712), but if selectivity of column is quite high ('where deptno=10'), optimizer may consider doing Full table scan. With ALL_ROWS, optimizer has more freedom to its job at its best. 2. Good for OLAP system, where work happens in batches/procedures. (While some of the report may still use FIRST_ROWS depending upon the anxiety level of report reviewers) 3. Likes hash joins over nested loop for larger data sets. Conclusion Cost based optimizer gives you flexibility to choose response time or throughput. So use them based on your business requirement.

first_rows and all_rows are CBO only modes, this is true. In choose mode, the optimizer will be closer to an all_rows optimization. First_rows attempts to optimize the query to get the very first row back to the client as fast as possible. This is good for an interactive client server environment where the client runs a query and shows the user the first 10 rows or so and waits for them to page down to get more. All_rows attempts to optimize the query to get the very last row as fast as possible. This makes sense in a stored procedure for example where the client does not regain control until the stored procedure completes. You don't care if you have to wait to get the first row if the last row gets back to you twice as fast. In a client server/interactive application you may well care about that.

Dion Cho Oracle Performance Storyteller


We are natural born scientists

FIRST_ROWS vs. ALL_ROWS and ROWNUM predicate


leave a comment A quite famous problem(especially on 9i-) on FIRST_ROWS vs. ALL_ROWS: 1. { Select } query has an execution plan which performs index range scan. 2. But when same { Select } is used as a part of DML statements like { Insert .. Select }, Oracle simply ignores to use effective index. This was often due to the fact that Oracle always uses ALL_ROWS mode internally for DML statement. This means that even when you choose to use FIRST_ROWS mode on system level, your DML statements stick to ALL_ROWS mode. This also means that when optimizer mode is ALL_ROWS, there should be no diffference between select statement and DML statement. This is a natural assumption, isnt it? But lets see following non-intuitive result.
UKJA@ukja102> select * from v$version; BANNER -------------------------------------------------------------------Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod PL/SQL Release 10.2.0.1.0 - Production CORE 10.2.0.1.0 Production TNS for 32-bit Windows: Version 10.2.0.1.0 - Production NLSRTL Version 10.2.0.1.0 - Production UKJA@ukja102> show parameter optimizer_mode NAME TYPE VALUE ------------------------ ------ ------optimizer_mode string ALL_ROWS UKJA@ukja102> create table t1 ( 2 c1 varchar2(10), 3 c2 number, 4 c3 number, 5 constraint t1_pk primary key (c2, c1) 6 ); Table created. UKJA@ukja102> UKJA@ukja102> insert into t1 2 select 3 dbms_random.string('x', 10), 4 case when level <= 5000 then 1 else 0 end, 5 level 6 from dual 7 connect by level <= 10000 8 ; 10000 rows created. UKJA@ukja102> exec dbms_stats.gather_table_stats(user, 't1');

PL/SQL procedure successfully completed. UKJA@ukja102> -- default UKJA@ukja102> explain plan for 2 select * 3 from t1 4 where c2 = 0 and rownum = 1 5 ; Explained. UKJA@ukja102> select * from table(dbms_xplan.display); ------------------------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 1 | 17 | 3 (0)| 00:00:01 | |* 1 | COUNT STOPKEY | | | | | | | 2 | TABLE ACCESS BY INDEX ROWID| T1 | 1 | 17 | 3 (0)| 00:00:01 | |* 3 | INDEX RANGE SCAN | T1_PK | | | 2 (0)| 00:00:01 | ------------------------------------------------------------------------------------Predicate Information (identified by operation id): --------------------------------------------------1 - filter(ROWNUM=1) 3 - access("C2"=0) 16 rows selected. UKJA@ukja102> explain plan for 2 update t1 set c2 = 1 3 where c2 = 0 and rownum = 1 4 ; Explained. UKJA@ukja102> select * from table(dbms_xplan.display); ---------------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------| 0 | UPDATE STATEMENT | | 1 | 14 | 11 (0)| 00:00:01 | | 1 | UPDATE | T1 | | | | | |* 2 | COUNT STOPKEY | | | | | | |* 3 | TABLE ACCESS FULL| T1 | 5000 | 70000 | 11 (0)| 00:00:01 | ---------------------------------------------------------------------------Predicate Information (identified by operation id): --------------------------------------------------2 - filter(ROWNUM=1) 3 - filter("C2"=0) 16 rows selected.

See that even under ALL_ROWS mode, the select statement and update statement generate different execution plans? Whats happening here?

This is due to the fact that Oracle converts ROWNUM predicate to first_rows mode internally. This behavior is controlled by _optimizer_rownum_pred_based_fkr hidden parameter, whose default value is true. This means that even under ALL_ROWS mode, Oracle tries to stick to FIRST_ROWS mode when ROWNUM predicate being used. Following test case clearly shows that with _optimizer_rownum_pred_based_fkr being false, select statement generates same execution plan with update statement.
2 3 4 5 UKJA@ukja102> explain plan for select /*+ opt_param('_optimizer_rownum_pred_based_fkr', 'false') */ * from t1 where c2 = 0 and rownum = 1 ;

Explained. UKJA@ukja102> select * from table(dbms_xplan.display); --------------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------| 0 | SELECT STATEMENT | | 1 | 17 | 11 (0)| 00:00:01 | |* 1 | COUNT STOPKEY | | | | | | |* 2 | TABLE ACCESS FULL| T1 | 5000 | 85000 | 11 (0)| 00:00:01 | --------------------------------------------------------------------------Predicate Information (identified by operation id): --------------------------------------------------1 - filter(ROWNUM=1) 2 - filter("C2"=0) 15 rows selected.

Quite annoying, but this is how Oracle impoves the CBO. The last question is how to make update statement choose efficient index range scan other than inefficient table full scan? Maybe the only reasonable solution is to use appropriate hints.
2 3 4 UKJA@ukja102> explain plan for update /*+ index(t1) */ t1 set c2 = 1 where c2 = 0 and rownum = 1 ;

Explained. UKJA@ukja102> select * from table(dbms_xplan.display); ---------------------------------------------------------------------------| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------| 0 | UPDATE STATEMENT | | 1 | 14 | 23 (0)| 00:00:01 | | 1 | UPDATE | T1 | | | | | |* 2 | COUNT STOPKEY | | | | | | |* 3 | INDEX RANGE SCAN| T1_PK | 5000 | 70000 | 23 (0)| 00:00:01 | ---------------------------------------------------------------------------Predicate Information (identified by operation id): --------------------------------------------------2 - filter(ROWNUM=1) 3 - access("C2"=0) 16 rows selected.

You might also like