Mohamed Houri’s Oracle Notes

July 7, 2018

Subquery unnesting that does not require costing

Filed under: Subquery Unnesting — hourim @ 6:16 am

There are two types of subquery unnesting: the first one is merged by the CBO with its outer query and the second one is turned into an inline view before being, eventually, merged with its outer query. While the latter is cost based, the former is imperatively done provided it passes the subquery unnesting validity check. This is why, we may, sometimes, pay a performance penalty because of the automatic subquery unnesting.

Automatic subquery unnesting that does not require costing

This is precisely what I have experienced at one of my customer sites:

insert into t1_targ
     (id)
select
     t2_id
from
     t2 lt1
where
     t2.l_id > 0
and  t2.status in (0,1,3)
and  t2.class = 'XyyZgg'
and EXISTS
         (select null
          from t2 lt2
          where lt2.t1_id  = lt1.l_id
          and   lt2.status = 2
          and   lt2.class != 'XyyZgg'
          );
Global Stats
=========================================
| Elapsed |   Cpu   |  IO      | Buffer |
| Time(s) | Time(s) | Waits(s) |  Gets  |
=========================================
|    4070 |    343  |     3724 |   31M  |
=========================================

SQL Plan Monitoring Details (Plan Hash Value=1860787001)
===================================================================
| Id    |                 Operation                   |       Name |
|       |                                             |            |
===================================================================
|     0 | INSERT STATEMENT                            |            |
|     1 |   LOAD TABLE CONVENTIONAL                   | T1_ARG     |
|     2 |    HASH JOIN                                |            |
|     3 |     NESTED LOOPS                            |            |        
|     4 |      NESTED LOOPS                           |            |
|     5 |       STATISTICS COLLECTOR                  |            |
|     6 |        INLIST ITERATOR                      |            |
|     7 |         TABLE ACCESS BY INDEX ROWID BATCHED | T2         |
|     8 |          INDEX RANGE SCAN                   | IDX_T262   |
|     9 |       INDEX UNIQUE SCAN                     | PK_T250    |
|    10 |     TABLE ACCESS BY INDEX ROWID             | T2         |
|    11 |    TABLE ACCESS BY INDEX ROWID BATCHED      | T2         |
|    12 |     INDEX SKIP SCAN                         | IDX_T262   |
===================================================================

As you can see the EXISTS subquery has been unnested and merged into the body of the outer query to form a join as the following lines taken from the corresponding 10053 trace file prove:

*****************************
Cost-Based Subquery Unnesting
*****************************
SU: Unnesting query blocks in query block SEL$1 (#1) that are valid to unnest.
Subquery Unnesting on query block SEL$1 (#1)SU: Performing unnesting that does not require costing. -- automatic
SU: Considering subquery unnest on query block SEL$1 (#1).
SU:   Checking validity of unnesting subquery SEL$2 (#2)
SU:   Passed validity checks. -- subquery is valid for unnesting
SU:   Transforming EXISTS subquery to a join. -- unnesting is done here
Registered qb: SEL$5DA710D3 0x64c1aaf8 (SUBQUERY UNNEST SEL$1; SEL$2)

That is how the automatic subquery unnesting works: provided there are no restrictions pre-empting it, the unnesting will be automatically done.

if, I add a restriction (rownum <=10) into the above query, the unnesting will be bypassed as the following proves:

insert into t1_targ
     (id)
select
     t2_id
from
     t2 lt1
where
     t2.l_id > 0
and  t2.status in (0,1,3)
and  t2.class = 'XyyZgg';
and EXISTS
         (select null
          from t2 lt2
          where lt2.t1_id  = lt1.l_id
          and   lt2.status = 2
          and   lt2.class != 'XyyZgg';
          and   rownum <= 10 -- restriction
          );

===================================================================
| Id    |                 Operation                   |       Name |
===================================================================
|     0 | INSERT STATEMENT                            |            |
|     1 |   LOAD TABLE CONVENTIONAL                   | T1_ARG     |
|     2 |    INLIST ITERATOR                          |            |
|     3 |     TABLE ACCESS BY INDEX ROWID BATCHED     | T2         | 
|     4 |      INDEX RANGE SCAN                       | IDX_T262   |                       
|     5 |       COUNT STOPKEY                         |            |
|     6 |        TABLE ACCESS BY INDEX ROWID          | T2         |            
|     7 |         INDEX UNIQUE SCAN                   | PK_T250    |
===================================================================

*****************************
Cost-Based Subquery Unnesting
*****************************
SU: Unnesting query blocks in query block SEL$1 (#1) that are valid to unnest.
Subquery Unnesting on query block SEL$1 (#1)SU: Performing unnesting that does not require costing.
SU: Considering subquery unnest on query block SEL$1 (#1).
SU:   Checking validity of unnesting subquery SEL$2 (#2)
SU:     SU bypassed: Query block references ROWNUM. -- restriction
SU:   Validity checks failed.-- unnesting is bypassed

At my client site, however, automatically unnesting the subquery was a dramatic choice made by the CBO as the historical statistics of the corresponding insert/select displayed below clearly show (sometimes more than 14,000 seconds without completing):

@HistStats
Enter value for sql_id:6gryp77nfgtv8
Enter value for from_date: 15052018

SNAP_BEGIN                PLAN_HASH_VALUE EXECS END_OF_FETCH  AVG_ETIME  AVG_LIO  
------------------------- --------------- ----- ------------ ----------  --------
16-MAY-18 12.00.13.600 AM      1860787001     0            0      3607    2975544     
16-MAY-18 01.00.20.220 PM      1860787001     1            1      3062    2708725
16-MAY-18 02.00.41.960 PM      1860787001     0            0      2509    3374627
16-MAY-18 03.00.48.113 PM      1860787001     0            0      4169    4970849
16-MAY-18 04.00.54.446 PM      1860787001     0            0      7659   16659725
16-MAY-18 05.00.04.833 PM      1860787001     0            0     13342   21798355
16-MAY-18 06.00.14.538 PM      1860787001     0            0     14245   35578453
../...
17-MAY-18 08.00.59.398 PM      1860787001     0            0      3544    3627666

But immediately when I cancelled the unnesting (using /*+ no_unnest */) I got a better execution time as shown below:

@HistStats
Enter value for sql_id:6gryp77nfgtv8
Enter value for from_date: 17052018

SNAP_BEGIN                PLAN_HASH_VALUE EXECS END_OF_FETCH  AVG_ETIME  AVG_LIO  
------------------------- --------------- ----- ------------ ----------  --------
17-MAY-18 08.00.59.398 PM      1860787001     0            0     3544    3627666
18-MAY-18 11.00.30.847 AM          530894     2            2     116      528385  -- 116 sec/exec 

Cost based subquery unnesting

The subquery unnesting ceases, however, to be imperatively done whenever the subquery is transformed into an inline view. In this case, even when there is nothing preventing the subquery from being unnested, Oracle will evaluate the cost of unnesting it before proceeding to the transformation. Here’s below a typical illustration (the model is at the end of this blog post):

explain plan for
 select
     a.n1
 from
    t1 a
where
    a.n1 = 10
and exists
    (select 
	    null 
	 from
	    t1 b
	   ,t2 c
	 where
	    b.n1 = c.n1
	 and 
	    b.n2 = a.n3
    );

select * from table(dbms_xplan.display);

----------------------------------------------------------------
| Id  | Operation                    | Name    | Rows  | Bytes |
----------------------------------------------------------------
|   0 | SELECT STATEMENT             |         |     1 |     9 |
|   1 |  NESTED LOOPS SEMI           |         |     1 |     9 |
|   2 |   TABLE ACCESS BY INDEX ROWID| T1      |     1 |     7 |
|*  3 |    INDEX UNIQUE SCAN         | T1_PK   |     1 |       |
|   4 |   VIEW PUSHED PREDICATE      | VW_SQ_1 |     1 |     2 | -- subquery into view
|   5 |    NESTED LOOPS SEMI         |         |     1 |    11 |
|*  6 |     TABLE ACCESS FULL        | T1      |     3 |    21 |
|*  7 |     INDEX UNIQUE SCAN        | T2_PK   |   300 |  1200 |
----------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("A"."N1"=10)
   6 - filter("B"."N2"="A"."N3")
   7 – access("B"."N1"="C"."N1")

*****************************
Cost-Based Subquery Unnesting
*****************************
SU: Unnesting query blocks in query block SEL$1 (#1) that are valid to unnest.
Subquery Unnesting on query block SEL$1 (#1)SU: Performing unnesting that does not require costing.
SU: Considering subquery unnest on query block SEL$1 (#1).
SU:   Checking validity of unnesting subquery SEL$2 (#2)
SU:   Passed validity checks, but requires costing.  -- here where the CBO is costing the subquery unnesting
SU: Using search type: exhaustive
SU: Starting iteration 1, state space = (2) : (1)
SU:   Unnesting subquery query block SEL$2 (#2)Registered qb: SEL$683B0107 0x70409408 (SUBQ INTO VIEW FOR COMPLEX UNNEST SEL$2)

Summary

Don’t be surprised to see Oracle, sometimes, using a dramatic execution plan when it unnests a subquery. This might be related to the automatic unnesting which does not require costing. It is done by Oracle provided the subquery passes the unnesting validity check and is not transformed into an inline view prior to its unnesting.

create table t1
as select
    rownum n1
   ,trunc((rownum-1)/3) n2
   ,mod(rownum,10) n3
from dual
connect by level <= 3e2;

create table t2
as select
    2*rownum n1
   ,trunc(rownum,5) n2
   ,lpad('x',5) vc
from dual
connect by level <= 3e2;

alter table t1 add constraint t1_pk primary key (n1);
alter table t2 add constraint t2_pk primary key (n1);

Create a free website or blog at WordPress.com.

Tony's Oracle Tips

Tony Hasler's light hearted approach to learning about Oracle

Richard Foote's Oracle Blog

Focusing Specifically On Oracle Indexes, Database Administration and Some Great Music

Hatem Mahmoud's blog

Just another blog : Databases, Linux and other stuffs

Mohamed Houri’s Oracle Notes

Qui se conçoit bien s’énonce clairement

Oracle Diagnostician

Performance troubleshooting as exact science

Raheel's Blog

Things I have learnt as Oracle DBA

Coskan's Approach to Oracle

What I learned about Oracle

So Many Oracle Manuals, So Little Time

“Books to the ceiling, Books to the sky, My pile of books is a mile high. How I love them! How I need them! I'll have a long beard by the time I read them”—Lobel, Arnold. Whiskers and Rhymes. William Morrow & Co, 1988.

Carlos Sierra's Tools and Tips

Tools and Tips for Oracle Performance and SQL Tuning

Oracle Scratchpad

Just another Oracle weblog

OraStory

Dominic Brooks on Oracle Performance, Tuning, Data Quality & Sensible Design ... (Now with added Sets Appeal)