A recent posting on OTN raised the question of whether or not the “parallel” hint and the “first_rows(n)” hint were mutually incompatible. This reminded me that from time to time other posters on OTN (copying information from various websites, perhaps) have claimed that “parallel doesn’t work with first rows” or, conversely, “first rows doesn’t work with parallel”. This is one of those funny little myths that is so old that the script I’ve got to demonstrate the misconception is dated 2003 with a first test version of 8.1.7.4.
Since I haven’t run the test on any version of Oracle newer than 9.2.0.4 I thought it was time to dust it down, modernise it slightly, and run it again. So here’s the bit that creates a sample data set:
create table t1 ( id number, v1 varchar2(10), padding varchar2(100), constraint t_pk primary key(id) using index local ) partition by range(id) ( partition p1000 values less than (1000), partition p2000 values less than (2000), partition p3000 values less than (3000), partition p4000 values less than (4000), partition p5000 values less than (5000) ) ; insert into t1 select rownum - 1, rpad(rownum-1,10), rpad('x',100) from all_objects where rownum <= 5000 -- > hint to avoid WordPress formatting issue order by dbms_random.value ; begin dbms_stats.gather_table_stats( ownname => user, tabname =>'T1', method_opt => 'for all columns size 1' ); end; /
Now I’m going to run a simple query, hinted in 4 different ways:
- no hints
- parallel hint only: /*+ parallel */
- first_rows(1) hint only: /*+ first_rows(1) */
- parallel and first_rows(1): /*+ parallel first_rows(1) */
Here’s the version of the query that has both hints in place:
set serveroutput off set linesize 156 set pagesize 60 set trimspool on select /*+ parallel first_rows(1) */ v1 from t1 where id between 1500 and 2000 ; select * from table(dbms_xplan.display_cursor(null,null,'cost outline'));
I’ve actually run the query and used the display_cursor() option to pull the plan from memory – in the original (8i) script I used autotrace and the old (deprecated, backwards compatibility only) first_rows hint. To do any other tests just clone and edit. Here are the 4 outputs from the call to display_cursor() – with a little cosmetic editing:
SQL_ID 63qnzam9b8m9g, child number 0 ===================================== select /*+ */ v1 from t1 where id between 1500 and 2000 Plan hash value: 277861402 ------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | ------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 15 (100)| | | | | 1 | PARTITION RANGE ITERATOR| | 502 | 7530 | 15 (0)| 00:00:01 | 2 | 3 | |* 2 | TABLE ACCESS FULL | T1 | 502 | 7530 | 15 (0)| 00:00:01 | 2 | 3 | ------------------------------------------------------------------------------------------------- Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('11.2.0.4') DB_VERSION('11.2.0.4') ALL_ROWS OUTLINE_LEAF(@"SEL$1") FULL(@"SEL$1" "T1"@"SEL$1") END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 2 - filter(("ID"<=2000 AND "ID">=1500)) SQL_ID ahary3u8q88mq, child number 1 ===================================== select /*+ parallel */ v1 from t1 where id between 1500 and 2000 Plan hash value: 9959369 ------------------------------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib | ------------------------------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | | | 8 (100)| | | | | | | | 1 | PX COORDINATOR | | | | | | | | | | | | 2 | PX SEND QC (RANDOM)| :TQ10000 | 502 | 7530 | 8 (0)| 00:00:01 | | | Q1,00 | P->S | QC (RAND) | | 3 | PX BLOCK ITERATOR | | 502 | 7530 | 8 (0)| 00:00:01 | 2 | 3 | Q1,00 | PCWC | | |* 4 | TABLE ACCESS FULL| T1 | 502 | 7530 | 8 (0)| 00:00:01 | 2 | 3 | Q1,00 | PCWP | | ------------------------------------------------------------------------------------------------------------------------------ Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('11.2.0.4') DB_VERSION('11.2.0.4') ALL_ROWS SHARED(2) OUTLINE_LEAF(@"SEL$1") FULL(@"SEL$1" "T1"@"SEL$1") END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 4 - access(:Z>=:Z AND :Z<=:Z) filter(("ID"<=2000 AND "ID">=1500)) Note ----- - automatic DOP: Computed Degree of Parallelism is 2 SQL_ID 3m6mnk9b337dd, child number 0 ===================================== select /*+ first_rows(1) */ v1 from t1 where id between 1500 and 2000 Plan hash value: 1044541683 ----------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | ----------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 6 (100)| | | | | 1 | PARTITION RANGE ITERATOR | | 4 | 60 | 6 (0)| 00:00:01 | 2 | 3 | | 2 | TABLE ACCESS BY LOCAL INDEX ROWID| T1 | 4 | 60 | 6 (0)| 00:00:01 | 2 | 3 | |* 3 | INDEX RANGE SCAN | T_PK | | | 2 (0)| 00:00:01 | 2 | 3 | ----------------------------------------------------------------------------------------------------------- Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('11.2.0.4') DB_VERSION('11.2.0.4') FIRST_ROWS(1) OUTLINE_LEAF(@"SEL$1") INDEX_RS_ASC(@"SEL$1" "T1"@"SEL$1" ("T1"."ID")) END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 3 - access("ID">=1500 AND "ID"<=2000) -- > needs edit to avoid WordPress formatting issue SQL_ID 9asm7t1zbv4q8, child number 1 ===================================== select /*+ parallel first_rows(1) */ v1 from t1 where id between 1500 and 2000 Plan hash value: 4229065483 ---------------------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | TQ |IN-OUT| PQ Distrib | ---------------------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 3 (100)| | | | | | | | 1 | PX COORDINATOR | | | | | | | | | | | | 2 | PX SEND QC (RANDOM) | :TQ10000 | 4 | 60 | 3 (0)| 00:00:01 | | | Q1,00 | P->S | QC (RAND) | | 3 | PX PARTITION RANGE ITERATOR | | 4 | 60 | 3 (0)| 00:00:01 | 2 | 3 | Q1,00 | PCWC | | | 4 | TABLE ACCESS BY LOCAL INDEX ROWID| T1 | 4 | 60 | 3 (0)| 00:00:01 | 2 | 3 | Q1,00 | PCWP | | |* 5 | INDEX RANGE SCAN | T_PK | | | 1 (0)| 00:00:01 | 2 | 3 | Q1,00 | PCWP | | ---------------------------------------------------------------------------------------------------------------------------------------------- Outline Data ------------- /*+ BEGIN_OUTLINE_DATA IGNORE_OPTIM_EMBEDDED_HINTS OPTIMIZER_FEATURES_ENABLE('11.2.0.4') DB_VERSION('11.2.0.4') FIRST_ROWS(1) SHARED(2) OUTLINE_LEAF(@"SEL$1") INDEX_RS_ASC(@"SEL$1" "T1"@"SEL$1" ("T1"."ID")) END_OUTLINE_DATA */ Predicate Information (identified by operation id): --------------------------------------------------- 5 - access("ID">=1500 AND "ID"<=2000) Note ----- - automatic DOP: Computed Degree of Parallelism is 2
Critically we get four different execution plans from the four different strategies – so clearly the optimizer is perfectly happy to accept the parallel and first_rows() hints simultaneously. Note, particularly, how the first_rows(1) hint when combined with the parallel hint moved us from a parallel full tablescan to a parallel index range scan.
Whether or not it’s sensible to use the hint combination in this way is a matter for careful consideration, of course, but there could be circumstances where the combination really is the best way to get the starting row(s) from a query that otherwise has to return a large amount of data.
