Another little warning for those about to upgrade from 9i to 10g. Some hints will change their behaviour. Consider the following SQL.
rem rem Script: push_subq.sql rem Author: Jonathan Lewis rem Dated: Sept 2003 rem select /*+ push_subq */ par.small_vc1, chi.small_vc1 from parent par, child chi where par.id1 between 1001 and 2200 and chi.id1 = par.id1 and exists ( select /*+ no_unnest qb_name(subq1) */ null from subtest sub1 where sub1.id1 = par.id1 and sub1.small_vc1 = par.small_vc1 and sub1.small_vc2 >= '2' ) ;
By default if Oracle cannot unnest a subquery it postpones execution of that subquery to the end of the execution plan. I’ve used the no_unnest hint in the subquery to stop Oracle from unnesting it.
In the absence of the push_subq hint Oracle would join parent to child then run the subquery. With the push_subq hint in place Oracle will acquire data from parent, run the subquery (which correlates to the parent), then join any parent row that survives the subquery to child.
At least, that’s how it works until I get to 10g.
Historically the push_subq hint told Oracle to run any outstanding subqueries at the earliest possible moment. But in 10g this changes (for the better). Imagine I have two subqueries in the query; it is quite possible that the optimum execution plan is to run one early and the other late, but the original implementation of push_subq is ‘all or nothing’. 10g allows you to be selective about which subqueries should be pushed.
There are two options. I can simply move the push_subq hint into each of the subqueries that I want pushed or I can use a ‘parameterized’ version of the push_subq hint in the main query block.
You will notice that I have used the qb_name() hint (a new 10g hint) in the subquery to give the subquery the name “subq1”. Because the query block is now named, I can reference it by name in other hints. So I can leave my existing push_subq hint where it is, but change it to read:
push_subq(@subq1)
(The “@” sign is necessary, not a typing error). If I have other subqueries that I want pushed I can create an individual hint for each of them, naming each in turn.
It doesn’t really matter which option you choose, I can think of good arguments for either, the important point is that if you are currently using the push_subq hint you will have to change your code when you upgrade to 10g.
Not surprisingly, the no_push_subq hint (there are some subqueries that Oracle will automatically run early) follows the same pattern. There may be other hints that have changed in similar ways, but I haven’t gone looking for them yet.
Footnote
Execution plan with “pushed” subquery. (See also my reply to comment 10 below for a detailed explanation of reading the plan – which includes the predicate, object/alias, and outline sections from 12cR2)
------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost | ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 139 | 2502 | 101 | |* 1 | HASH JOIN | | 139 | 2502 | 99 | |* 2 | TABLE ACCESS FULL | PARENT | 50 | 450 | 20 | |* 3 | TABLE ACCESS BY INDEX ROWID| SUBTEST | 1 | 14 | 2 | |* 4 | INDEX UNIQUE SCAN | SUB_PK | 1 | | 1 | |* 5 | TABLE ACCESS FULL | CHILD | 8011 | 72099 | 78 | -------------------------------------------------------------------------
Execution plan with a no_push_subq hint in the subquery
------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost | ------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | 18 | 2881 | |* 1 | FILTER | | | | | |* 2 | HASH JOIN | | 2781 | 50058 | 99 | |* 3 | TABLE ACCESS FULL | PARENT | 1001 | 9009 | 20 | |* 4 | TABLE ACCESS FULL | CHILD | 8011 | 72099 | 78 | |* 5 | TABLE ACCESS BY INDEX ROWID| SUBTEST | 1 | 14 | 2 | |* 6 | INDEX UNIQUE SCAN | SUB_PK | 1 | | 1 | ------------------------------------------------------------------------
Jonathan, is the old “/*+ push_subq */” (without any argument) ignored in 10g ? I haven’t understood whether the new syntax is mandatory or simply a fine-grained additional variation. Thanks!
Comment by Alberto Dell'Era — March 10, 2007 @ 9:44 am GMT Mar 10,2007 |
I’ve noticed this change with push_subq some time ago and i was using the method with push_subq used in subquery. But method with naming subquery and referencing it name in other hint is nice solution.
Comment by Paweł Barut — March 10, 2007 @ 4:53 pm GMT Mar 10,2007 |
Alberto, the old syntax no longer works.
Comment by Jonathan Lewis — March 11, 2007 @ 7:09 am GMT Mar 11,2007 |
Hi
I have a question about push_subq.
I can see that it works as explained with exist.doest it work ,in case of NOT EXISTS as well.
I have a query like this – returns 14 rows,takes almost 90 mins.
– my questiones are:
how does push_subq will help or rathe is it helping or is it a valid choice here.
the tables in NOT EXISTS have lots of data.
is there a way I can improve the performance of this query.
Thanks
Ajeet
Comment by Ajeet — April 4, 2008 @ 2:24 pm BST Apr 4,2008 |
Ajeet,
Sorry, the supplied data is not formatted for easy reading. See the link to “how to comment” at top-right if you want to try again. [Now fixed – jl]
The only thing I will say is that push_subq applies to subqueries that are not ‘unnested’ – which means that appear in the execution plans as filter operations. I didn’t see an explicit filter in your execution plan – but sometimes the filter operation is not displayed.
Comment by Jonathan Lewis — April 16, 2008 @ 7:11 pm BST Apr 16,2008 |
Hi Jonathan
I read about query transformation in your book.
I could not uderstand why the query below is not using HASH_AJ for the not exists subquery .
the query below runs for 1 hr to return 50K rows.
could you please suggest what is wrong here –
database version is oracle 9iR2.
Comment by Ajeet — May 7, 2008 @ 2:42 pm BST May 7,2008 |
Ajeet,
I pointed out that your last commented hadn’t been formatted for readability – and the same applies to this one. [Now fixed – jl]
Ignoring the huge mess of the query and tkprof output, though, your question is “why hasn’t Oracle changed a “not exists” subquery to an anti-join.
You don’t need a big example to investigate this. A small example with just two tables will let you investigate that one detail and then allow you to apply the concept to more complex cases.
Off the top of my head, there are probably only two things to investigate. (a) maybe it’s the arithmetic, (b) maybe it relates to the need for “not null” constraints on the correlating columns. (And (a2) if you want to hint it, then checking that you’ve definitely used the hints correctly).
Comment by Jonathan Lewis — May 8, 2008 @ 7:41 am BST May 8,2008 |
Jonathan
adding a not null constraint in the subquery has indeed converted that into hash anti join,
infact in the “Cost based fundamentals” also you have written – but i was confused as it was discussed for NOT IN.
sorry about poor formating.
I will pay attention on this in future postings.
Thanks
Comment by Ajeet — May 13, 2008 @ 7:15 am BST May 13,2008 |
[…] push_subq: how upgrades can stop hints working. […]
Pingback by Rules for Hinting « Oracle Scratchpad — December 2, 2010 @ 8:57 am GMT Dec 2,2010 |
Hi Jonathan,
Thanks for the post.Looks like interpreting plans with pushed subqueries is tricky.
How can i interpret this plan and the order of execution.
Execution plan with “pushed” subquery
Thank you,
sri
Comment by sri — May 20, 2020 @ 3:50 pm BST May 20,2020 |
Sri,
Here’s the plan (from 12.2.0.1, so some differences in numbers) with the predciate, outline and alias information added.
The query block/ object alias information shows you that operations 3 and 4 make up a separate query block (sel$2) in the final query, and the predicate information shows you that operation 2 includes a FILTER() subquery which includes the hints /*+ no_unnest push_subq */.
From this you should see that order of activity is;
operation 1:
Call first child (operation 2) to supply data for the build table
operation 2:
Table scan of table parent, for each row execute the existence subquery (operations 3 and 4); for each row that survives the existence test pass the row up to operation 1.
operation 1
Call second child (operation 5) to supply probe table
operation 5
Table scan of child selecting relevant rows, pass resulting rows up to operation 1
operation 1
For each row from operation 5 probe the in-memory build table; for each row that survives pass up to operaiton 0 / client.
Order of rowsource generation: 2, 4, 3, 5, 1
Comment by Jonathan Lewis — May 20, 2020 @ 4:46 pm BST May 20,2020 |
Hello Jonathan,
Thank you for the post. We have very limited information about the subquery plan interpretation.
From the outline section,we have
What about the first one OUTLINE_LEAF(@”SEL$1″)–>
From query block section,it shows both
and
Please clarify.
Regards,
Sandeep.
Comment by Sandeep — August 16, 2023 @ 12:26 pm BST Aug 16,2023 |
The outline_leaf() items are the final set of query blocks that the optimizes has produced “sub-plans” for before stitching the sub-plans together to produce the final full execution plan. (The outline() items are the original query blocks that the optimizer sees in the SQL as you’ve written it – sometimes, as in this case, the two lists are identical.)
It’s not always straightforward to unstitch the query blocks in the plan, and the plan doesn’t always report a query block name for every operation, but in this case it’s quite simple.
If you look at just the sel$1 operations you get the following plan – which is telling you that the main body of the plan is a hash join with tablescans.
If you look at the just the sel$2 operations you get the following:
This tells you how the subquery was handled.
We should know that a filter subquery (nominally) executes once for each from “somwhere”, so when we look at the two sub-plans together we can infer that that subquery executes once for each row returned from the tablescan in operation 2, before it is passed up (assuming it passes the subquery test) to operation 1.
Regards
Jonathan Lewis
Comment by Jonathan Lewis — August 16, 2023 @ 1:44 pm BST Aug 16,2023 |