Skip to Main Content
  • Questions
  • View Push Predicate not being used when DB Link is used

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, gabriele.

Asked: June 06, 2019 - 11:12 am UTC

Last updated: June 06, 2019 - 3:16 pm UTC

Version: 12.1.0.2

Viewed 1000+ times

You Asked

Hi all,
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.created 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:
-----------------------------------------------------------------------------------------------------------------------
| 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"='*'))


Q1 performance issues happen when the line --and exists(select 1 from dual@db2) in the view is uncommented. Q1 new execution plan is:
------------------------------------------------------------------------------------------------------------------------------------------------------------
| 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 execute Q1, stop Q1 (after about 4 minutes) and then got the plan.

and exists(select 1 from dual@db2) was used to show that the my 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).

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 on Q1.

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

Thank you

and Chris said...

Some observations:

- Q1 uses a nested loop to join ORDERS to the view. Q2 uses a hash join. Hash joins can't apply predicates from the first table to the second

- The first (fast) query only returns 9 rows from orders. The second more than 25,000. This increase in row count could be enough for an adaptive plan to switch from NL -> HJ.

- The remote query is in your select clause. So it's executed once for every row q_inner returns. Which is 1.2M and counting! Converting this to an outer join may help

- The queries seem to be accessing ORDERS twice. Joining on ID so both get the same row! It seems to me you could simplify the query to avoid one of these accesses

So I'd start by changing the query to only access ORDERS once. So you're only fetching rows from ORDERS for today. This in itself may be enough to solve the issue!

I don't understand what the view's doing well enough to say how; if you need help with this submit a new question with:

- create tables
- sample data in the form of insert intos
- expected query result

Is this answer out of date? If it is, please let us know via a Comment

More to Explore

Performance

Get all the information about database performance in the Database Performance guide.