1

I'm having performance issues executing the following query (Q1):

select
    z_out.*,
    a_out.id
from orders a_out, test z_out
where a_out.id=z_out.id and a_out.created>trunc(sysdate) and rownum<10

Table orders contains millions of rows; orders.id is the primary key and orders.craeted is indexed.

The view is:

create or replace view test as 
select/*+qb_name(q_outer)*/
    id,
    min(value) keep (dense_rank first order by id) as value
from (
    select/*+qb_name(q_inner)*/
        id, 
        case
            when substr(id, -1)<'5' 
                --and exists(select 1 from dual@db2)
                then 'YYY'
        end as attr_1
    from orders a1
) a2, small_table b2
where b2.attr_1 in (nvl(a2.attr_1, '#'), '*')
group by id

where small_table b2 contains about 200 records (all the columns are varchar2).

Executing Q1 has great performances and the following execution plan:

Plan hash value: 2906430222                                                                                                  

-----------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name                | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                     |     1 |   274 |    64   (0)| 00:00:01 |       |       |
|*  1 |  COUNT STOPKEY                        |                     |       |       |            |          |       |       |
|   2 |   NESTED LOOPS                        |                     |     1 |   274 |    64   (0)| 00:00:01 |       |       |
|   3 |    PARTITION LIST ALL                 |                     |     1 |    22 |    59   (0)| 00:00:01 |     1 |     2 |
|   4 |     PARTITION RANGE ALL               |                     |     1 |    22 |    59   (0)| 00:00:01 |     1 |  LAST |
|   5 |      TABLE ACCESS BY LOCAL INDEX ROWID| ORDERS              |     1 |    22 |    59   (0)| 00:00:01 |     1 |    29 |
|*  6 |       INDEX RANGE SCAN                | IDX_ORDERS_CREATED  |     1 |       |    57   (0)| 00:00:01 |     1 |    29 |
|   7 |    VIEW PUSHED PREDICATE              | TEST                |     1 |   252 |     5   (0)| 00:00:01 |       |       |
|*  8 |     FILTER                            |                     |       |       |            |          |       |       |
|   9 |      SORT AGGREGATE                   |                     |     1 |    55 |            |          |       |       |
|  10 |       NESTED LOOPS                    |                     |   259 | 14245 |     5   (0)| 00:00:01 |       |       |
|* 11 |        INDEX UNIQUE SCAN              | PK_ID               |     1 |    14 |     2   (0)| 00:00:01 |       |       |
|* 12 |        INDEX STORAGE FAST FULL SCAN   | IDX_MN_AN_AD_ALL    |   259 | 10619 |     3   (0)| 00:00:01 |       |       |
-----------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):                                                                          
---------------------------------------------------                                                                          

   1 - filter(ROWNUM<10)                                                                                                     
   6 - access("A_OUT"."CREATED">TRUNC(SYSDATE@!))                                                                     
   8 - filter(COUNT(*)>0)                                                                                                    
  11 - access("ID"="A_OUT"."ID")                                                                                       
  12 - storage("B2"."ATTR_1"=NVL(CASE  WHEN SUBSTR("ID",(-1))<'5' THEN 'YYY' END ,'#') OR "B2"."ATTR_1"='*')              
       filter("B2"."ATTR_1"=NVL(CASE  WHEN SUBSTR("ID",(-1))<'5' THEN 'YYY' END ,'#') OR "B2"."ATTR_1"='*')               

Q1 performance issues happen when the line --and exists(select 1 from dual@db2) in the view is uncommented. The new execution plan is:

Plan hash value: 3271081243                                                                                                                   

----------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name                | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop | Inst   |IN-OUT|
----------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                     |     1 |   288 |  5273K  (1)| 00:03:27 |       |       |        |      |
|*  1 |  COUNT STOPKEY                         |                     |       |       |            |          |       |       |        |      |
|*  2 |   HASH JOIN                            |                     |     1 |   288 |  5273K  (1)| 00:03:27 |       |       |        |      |
|   3 |    JOIN FILTER CREATE                  | :BF0000             |     1 |    22 |    59   (0)| 00:00:01 |       |       |        |      |
|   4 |     PARTITION LIST ALL                 |                     |     1 |    22 |    59   (0)| 00:00:01 |     1 |     2 |        |      |
|   5 |      PARTITION RANGE ALL               |                     |     1 |    22 |    59   (0)| 00:00:01 |     1 |  LAST |        |      |
|   6 |       TABLE ACCESS BY LOCAL INDEX ROWID| ORDERS              |     1 |    22 |    59   (0)| 00:00:01 |     1 |    29 |        |      |
|*  7 |        INDEX RANGE SCAN                | IDX_ORDERS_CREATED  |     1 |       |    57   (0)| 00:00:01 |     1 |    29 |        |      |
|   8 |    VIEW                                | TEST                |  3840K|   974M|  5273K  (1)| 00:03:27 |       |       |        |      |
|   9 |     SORT GROUP BY                      |                     |  3840K|   201M|  5273K  (1)| 00:03:27 |       |       |        |      |
|  10 |      JOIN FILTER USE                   | :BF0000             |   994M|    50G|  5273K  (1)| 00:03:27 |       |       |        |      |
|  11 |       NESTED LOOPS                     |                     |   994M|    50G|  5273K  (1)| 00:03:27 |       |       |        |      |
|  12 |        INDEX FULL SCAN                 | PK_ID               |  3840K|    51M| 66212   (1)| 00:00:03 |       |       |        |      |
|* 13 |        INDEX STORAGE FAST FULL SCAN    | IDX_MN_AN_AD_ALL    |   259 | 10619 |     1   (0)| 00:00:01 |       |       |        |      |
|  14 |         REMOTE                         |                     |       |       |            |          |       |       |    DB2 | R->S |
----------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):                                                                                           
---------------------------------------------------                                                                                           

   1 - filter(ROWNUM<10)                                                                                                                      
   2 - access("A_OUT"."ID"="Z_OUT"."ID")                                                                                                
   7 - access("A_OUT"."CREATED">TRUNC(SYSDATE@!))                                                                                      
  13 - filter("B2"."ATTR_1"=NVL(CASE  WHEN (SUBSTR("ID",(-1))<'5' AND  EXISTS (SELECT 0 FROM  "A1")) THEN 'YYY' END ,'#') OR               
              "B2"."ATTR_1"='*')                                                                                                              

Remote SQL Information (identified by operation id):                                                                                          
----------------------------------------------------                                                                                          

  14 - EXPLAIN PLAN INTO PLAN_TABLE@! FOR SELECT 0 FROM "DUAL" "A1" (accessing 'DB2' )                                                        

I would like the view to be accessed n times, like in the first scenario. I tried using hints but didn't succeed.

May be useful to say that even with the line and exists(select 1 from dual@db2) uncommented in the view, the following query has great performances (I know that is different from Q1).

select
    (select value from test z_out where a_out.id=z_out.id) as value,
    a_out.id
from orders a_out
where a_out.created>trunc(sysdate) and rownum<10

So, I guess the view works fine when it's accessed n times even if the line and exists(select 1 from dual@db2) is uncommented. But I'm not being able to force the execution plan in that direction.

If hints are necessary, I'd like to add them inside the view DDL only (if possible) so that who uses the view won't have to worry about it.

================================================================

Edit: the following were executed:

alter session set statistics_level = 'ALL';
-- Q1 (the query I'm having problems with)
select * from table (dbms_xplan.display_cursor (format=>'ALLSTATS LAST'));

Plan hash value: 3271081243                                                                                                                                 

------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name                | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                     |      1 |        |      0 |00:00:00.01 |       0 |      0 |       |       |          |
|*  1 |  COUNT STOPKEY                         |                     |      1 |        |      0 |00:00:00.01 |       0 |      0 |       |       |          |
|*  2 |   HASH JOIN                            |                     |      1 |      1 |      0 |00:00:00.01 |       0 |      0 |  3789K|  3789K| 1078K (0)|
|   3 |    JOIN FILTER CREATE                  | :BF0000             |      1 |      1 |  25602 |00:00:00.22 |   23345 |    161 |       |       |          |
|   4 |     PARTITION LIST ALL                 |                     |      1 |      1 |  25602 |00:00:00.21 |   23345 |    161 |       |       |          |
|   5 |      PARTITION RANGE ALL               |                     |      2 |      1 |  25602 |00:00:00.21 |   23345 |    161 |       |       |          |
|   6 |       TABLE ACCESS BY LOCAL INDEX ROWID| ORDERS              |     29 |      1 |  25602 |00:00:00.20 |   23345 |    161 |       |       |          |
|*  7 |        INDEX RANGE SCAN                | IDX_CREATED         |     13 |      1 |  25602 |00:00:00.12 |     474 |    161 |  1025K|  1025K|          |
|   8 |    VIEW                                | TEST                |      1 |   3820K|      0 |00:00:00.01 |       0 |      0 |       |       |          |
|   9 |     SORT GROUP BY                      |                     |      1 |   3820K|      0 |00:00:00.01 |       0 |      0 | 73728 | 73728 |          |
|  10 |      JOIN FILTER USE                   | :BF0000             |      1 |    989M|    106M|00:03:38.87 |      60M|  52960 |       |       |          |
|  11 |       NESTED LOOPS                     |                     |      1 |    989M|    328M|00:03:04.11 |      60M|  52960 |       |       |          |
|  12 |        INDEX FULL SCAN                 | PK_ID               |      1 |   3820K|   1245K|00:00:21.04 |     200K|  52959 |  1025K|  1025K|          |
|* 13 |        INDEX STORAGE FAST FULL SCAN    | IDX_MN_AN_AD_ALL    |   1245K|    259 |    328M|00:02:12.09 |      60M|      1 |  1025K|  1025K|          |
|  14 |         REMOTE                         |                     |      1 |        |      1 |00:00:00.01 |       0 |      0 |       |       |          |
------------------------------------------------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):                                                                                                         
---------------------------------------------------                                                                                                         

   1 - filter(ROWNUM<10)                                                                                                                                    
   2 - access("A_OUT"."ID"="Z_OUT"."ID")                                                                                                              
   7 - access("A_OUT"."CREATED">TRUNC(SYSDATE@!))                                                                                                    
  13 - filter(("B2"."ATTR_1"=NVL(CASE  WHEN (SUBSTR("ID",(-1))<'5' AND  IS NOT NULL) THEN 'YYY' END ,'#') OR "B2"."ATTR_1"='*'))                         

Note: Q1 performances prevent the query to complete if and exists(select 1 from dual@db2) in the view is uncommented. To get the previous execution plan I had to alter the session, run Q1, stop Q1 (after about 4 minutes) and then calculate the plan.

The following execution plan was generated the same way, but the view had the line --and exists(select 1 from dual@db2) commented (performances were good).

Plan hash value: 2906430222                                                                                            

-----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name                | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |                     |      1 |        |      9 |00:00:00.01 |     223 |
|*  1 |  COUNT STOPKEY                        |                     |      1 |        |      9 |00:00:00.01 |     223 |
|   2 |   NESTED LOOPS                        |                     |      1 |      1 |      9 |00:00:00.01 |     223 |
|   3 |    PARTITION LIST ALL                 |                     |      1 |      1 |      9 |00:00:00.01 |      41 |
|   4 |     PARTITION RANGE ALL               |                     |      1 |      1 |      9 |00:00:00.01 |      41 |
|   5 |      TABLE ACCESS BY LOCAL INDEX ROWID| ORDERS              |     14 |      1 |      9 |00:00:00.01 |      41 |
|*  6 |       INDEX RANGE SCAN                | IDX_CREATED         |     12 |      1 |      9 |00:00:00.01 |      33 |
|   7 |    VIEW PUSHED PREDICATE              | TEST                |      9 |      1 |      9 |00:00:00.01 |     182 |
|*  8 |     FILTER                            |                     |      9 |        |      9 |00:00:00.01 |     182 |
|   9 |      SORT AGGREGATE                   |                     |      9 |      1 |      9 |00:00:00.01 |     182 |
|  10 |       NESTED LOOPS                    |                     |      9 |    259 |   2376 |00:00:00.01 |     182 |
|* 11 |        INDEX UNIQUE SCAN              | PK_ID               |      9 |      1 |      9 |00:00:00.01 |      20 |
|* 12 |        INDEX STORAGE FAST FULL SCAN   | IDX_MN_AN_AD_ALL    |      9 |    259 |   2376 |00:00:00.01 |     162 |
-----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):                                                                    
---------------------------------------------------                                                                    

   1 - filter(ROWNUM<10)                                                                                               
   6 - access("A_OUT"."CREATED">TRUNC(SYSDATE@!))                                                               
   8 - filter(COUNT(*)>0)                                                                                              
  11 - access("ID"="A_OUT"."ID")                                                                                 
  12 - storage(("B2"."ATTR_1"=NVL(CASE  WHEN SUBSTR("ID",(-1))<'5' THEN 'YYY' END ,'#') OR                          
              "B2"."ATTR_1"='*'))                                                                                      
       filter(("B2"."ATTR_1"=NVL(CASE  WHEN SUBSTR("ID",(-1))<'5' THEN 'YYY' END ,'#') OR                           
              "B2"."ATTR_1"='*'))       
zyky2000
  • 23
  • 3
  • 1
    `select 1 from dual` always exists, this subquery doesn't make sense --> the condition `and exists(select 1 from dual@db2)` always evaluates to TRUE, so simply delete this condition from your query and you get rid of this problem.. – krokodilko May 28 '19 at 17:57
  • @krokodilko: The view `test` is just a simple example written to show the problem I'm having with a few lines of code (the real view is way more complex and has an execution plan of 500+ lines). `and exists(select 1 from dual@db2)` was used to show that the problem happens even when a condition as simple as that one is used (the "real" view I'm working with accesses `DB2` for some good reasons). – zyky2000 May 29 '19 at 06:17
  • 1
    Please run `alter session set statistics_level = 'ALL';` then run your query, then immediately run this query: `select * from table (dbms_xplan.display_cursor (format=>'ALLSTATS LAST')); ` and attach a result of last query to the question. This will generate real statistics of the query instead of estimated stats generated by simple explain plan. – krokodilko May 29 '19 at 15:45
  • @krokodilko: I applied the suggested procedure and edited the question adding both the execution plans (for `Q1` with the line `and exists(select 1 from dual@db2)` in the view uncommented and for `Q1` with the line in the view commented). Thank you – zyky2000 May 30 '19 at 15:13
  • `A-Time = 00:00:00.01`, that means 100 ms (milliseconds). In my opinion 100 ms is not a performance problem. `A-Rows = 2376 `, it means that this query is scanning only ~2 thousands rows, this is very very little for DBMS. Are you sure you want to optimize **this query**? I'm guessing you're trying to optimize a completely different query, if yes then post the plan of the query you want to optimize. You cannot tune Ferrari looking at the performance graph of Lamborgini. – krokodilko May 30 '19 at 16:12
  • @krokodilko I think you may have confused the two execution plans the OP added. The first plan is the bad plan, and runs for over 3 minutes. The second plan that runs in 100ms is the good version of the query. – Jon Heller May 30 '19 at 17:30

0 Answers0