{"id":93558,"date":"2022-03-18T21:16:46","date_gmt":"2022-03-18T21:16:46","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=93558"},"modified":"2022-03-21T15:24:23","modified_gmt":"2022-03-21T15:24:23","slug":"oracle-subquery-caching-and-subquery-pushing","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/oracle-subquery-caching-and-subquery-pushing\/","title":{"rendered":"Oracle subquery caching and subquery pushing"},"content":{"rendered":"<p><strong>Jonathan Lewis' continuing series on the Oracle optimizer and how it transforms queries into execution plans:<\/strong><\/p>\n<ol>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/transformations-by-the-oracle-optimizer\/\">Transformations by the Oracle Optimizer<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/the-effects-of-null-with-not-in-on-oracle-transformations\/\">The effects of NULL with NOT IN on Oracle transformations<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/oracle-subquery-caching-and-subquery-pushing\/\">Oracle subquery caching and subquery pushing<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/oracle-optimizer-removing-or-coalescing-subqueries\/\">Oracle optimizer removing or coalescing subqueries<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/oracle-databases\/oracle-optimizer-or-expansion-transformations\/\">Oracle optimizer Or Expansion Transformations<\/a><\/li>\n<\/ol>\n\n<p>So far, this series has examined the shape of some of the execution plans that Oracle\u2019s optimizer can produce when there are subqueries in the <code>where<\/code> clause. It was found that the optimizer will often \u201cunnest\u201d a subquery to produce a join rather than using a filtering operation that repeatedly runs the subquery.<\/p>\n<p>In this installment you\u2019ll see why you might want to stop the optimizer from unnesting some subqueries and also see a way to control where in the plan the optimizer positions each subquery, a detail of timing that can affect the overall workload quite significantly.<\/p>\n<p>The examples use the <code>emp<\/code> and <code>dept<\/code> tables from the <code>scott<\/code> schema <em>($ORACLE_HOME\/rdbms\/admin\/utlsampl.sql<\/em>), and the demonstrations run from SQL*Plus on Oracle 19.11.<\/p>\n<h2>Scalar subquery caching<\/h2>\n<p>In case you don\u2019t have access to an instance where you can create the <code>scott<\/code> schema, here\u2019s a query to give you the highlights of the data.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">select\r\n        d.deptno, d.dname,\r\n        count(sal) dept_size,\r\n        round(avg(e.sal + nvl(e.comm,0)),2) earnings_avg\r\nfrom\r\n        dept    d,\r\n        emp     e\r\nwhere\r\n        e.deptno(+) = d.deptno\r\ngroup by\r\n        d.deptno, d.dname\r\norder by\r\n        d.deptno\r\n\/<\/pre>\n<p>&nbsp;<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"578\" height=\"132\" class=\"wp-image-93559\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-13.png\" \/><\/p>\n<p>The <code>dept<\/code> table has 4 rows with a primary key on <code>deptno<\/code>. The <code>emp<\/code> table has 14 rows with a primary key on <code>empno<\/code> and a foreign key constraint to <code>dept<\/code> based on <code>deptno<\/code>. There are, however, no rows in the <code>emp<\/code> table for department 40. Both tables include a few columns that I haven\u2019t yet mentioned.<\/p>\n<p>Here\u2019s one way to write a query to report all details about all employees who earn more than the average for their department:<\/p>\n<pre class=\"lang:tsql decode:true \">break on deptno skip 1\r\n\r\nselect\r\n        \/*+ \r\n                qb_name(main)\r\n                gather_plan_statistics \r\n        *\/\r\n        *\r\nfrom    emp e1\r\nwhere   e1.sal + nvl(e1.comm,0) &gt; (\r\n                select  \/*+ qb_name(subq) *\/\r\n                        avg(e2.sal + nvl(e2.comm,0))\r\n                from    emp e2\r\n                where   e2.deptno = e1.deptno\r\n        )\r\norder by\r\n        e1.deptno, e1.empno\r\n\/\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>I\u2019ll just point out a couple of details about the conventions used in this query:<\/p>\n<ul>\n<li>Because I\u2019ve used the <code>emp<\/code> table twice in this query I\u2019ve used the same table <em>alias twice<\/em> but added a numeric suffix to make each use unique; then I\u2019ve used the relevant alias with <em>every<\/em> column reference<\/li>\n<li>The query consists of two query blocks, so I\u2019ve named each query block explicitly with a <code>qb_name<\/code> (<em>\u201cquery block name\u201d<\/em>) hint.<\/li>\n<\/ul>\n<p>Here\u2019s the result of the query:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"665\" height=\"160\" class=\"wp-image-93560\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-14.png\" \/><\/p>\n<p>And here\u2019s the execution plan that appeared by default on my instance (generated using the <em>autotrace<\/em> option from SQL*Plus):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"654\" height=\"267\" class=\"wp-image-93561\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-15.png\" \/><\/p>\n<p>The optimizer has decided to execute this query exactly as it is written, using a <em>correlated subquery<\/em> to filter out rows from an initial table scan of <code>emp<\/code>. Operation 2 (<em>FILTER<\/em>) calls its first child to get rows from <code>emp<\/code> then <em>for each row in turn<\/em> calls the subquery passing in the current <code>deptno<\/code> as the <em>correlation variable<\/em> (the bind variable <em>:B1<\/em> in the <em>Predicate Information<\/em>).<\/p>\n<p>Here\u2019s an important question: how many times does the correlated subquery actually run? Is it really once for every single employee, or can Oracle find a way to avoid some of the work at runtime while still following the structure of the plan?<\/p>\n<p>You\u2019ll notice that I\u2019ve included the <code>\/*+ gather_plan_statistics *\/<\/code> hint in the query. If I <code>set serveroutput off<\/code> and re-execute the query, I can make a call to <code>dbms_xplan.display_cursor()<\/code> with the <code>allstats last<\/code> format option to show the <em>rowsource execution statistics<\/em> \u2013 which look like this:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">set serveroutput off\r\n-- execute query here\r\nselect * from table(dbms_xplan.display_cursor(format=&gt;'allstats last'));<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"849\" height=\"165\" class=\"wp-image-93562\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-16.png\" \/><\/p>\n<p>As you can see from the <em>Starts<\/em> column for operations 4 and 5, the subquery ran only 3 times (once for each department in the <code>emp<\/code> table). This is an example of <em>scalar subquery caching<\/em> at its most effective.<\/p>\n<p>Each time the correlated subquery is called session \u201cremembers\u201d the input value (<code>deptno<\/code>) and result (<code>avg()<\/code>) of the call in a local cache and if the subquery is called with an input that has previously been used the session gets the result from this cache rather than actually running the subquery again and, as an extra little boost, doesn\u2019t even check the cache if the <code>deptno<\/code> for the current call is the same as the one for the immediately preceding call. This means that some queries can execute much faster than the execution plan would suggest, and that\u2019s a good argument for blocking subquery unnesting (or even rewriting your query) if you know your data well enough to be certain that the caching feature will work in your favour. There\u2019s a note that expands on this observation at: <a href=\"https:\/\/jonathanlewis.wordpress.com\/2006\/11\/06\/filter-subqueries\/\">https:\/\/jonathanlewis.wordpress.com\/2006\/11\/06\/filter-subqueries\/<\/a><\/p>\n<p>There\u2019s a threat hidden behind the benefit, though, a threat that is a side effect of the way that Oracle Corp. has implemented the caching mechanism.<\/p>\n<p>For many years I used to do a demonstration where I updated one row in a much larger <code>emp<\/code> table after which a query like the one above that had previously been taking just 10 milliseconds to complete suddenly took 20 seconds of CPU time using exactly the same execution plan. The problem was that I had carefully updated a specific row in a way that \u201cbroke\u201d the caching mechanism and resulted in the session running the subquery more than 3,000 times when it had previously been running it just 6 times.<\/p>\n<p>I can\u2019t do anything so dramatic on paper with this data set but can demonstrate the nature of the issue by recreating the data set after editing the script to modify one of the department numbers. Originally the departments were created by the statements:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">insert into dept values (10, 'ACCOUNTING', 'NEW YORK');\r\ninsert into dept values (20, 'RESEARCH',   'DALLAS');\r\ninsert into dept values (30, 'SALES',      'CHICAGO');\r\ninsert into dept values (40, 'OPERATIONS', 'BOSTON');<\/pre>\n<p>I\u2019m going to change this to:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">INSERT INTO DEPT VALUES (310,'ACCOUNTING', 'NEW YORK');\r\nINSERT INTO DEPT VALUES (20, 'RESEARCH',   'DALLAS');\r\nINSERT INTO DEPT VALUES (30, 'SALES',      'CHICAGO');\r\nINSERT INTO DEPT VALUES (40, 'OPERATIONS', 'BOSTON');<\/pre>\n<p>In the <code>emp<\/code> table, this means I\u2019ve also changed three occurrences of department 10 to department 310.<\/p>\n<p>With the modified data set nothing changes \u2013 except the number of times the subquery starts \u2013 here\u2019s the plan with rowsource execution stats from the modified data:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"842\" height=\"158\" class=\"wp-image-93563\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-17.png\" \/><\/p>\n<p>Note that the subquery (operations 4 and 5) has now reported 5 <em>starts<\/em> rather than 3.<\/p>\n<p>The reason for this is that the scalar subquery cache is based on hashing \u2013 and since hash tables are always of limited size you can end up with \u201chash collisions\u201d. I\u2019ve laboriously worked out that the hash function used by Oracle for scalar subquery caching makes 30 and 310 collide. When collisions occur the internal code for scalar subquery caching doesn\u2019t attempt to do anything clever like creating a \u201chash chain\u201d, it simply executes the subquery for the problematic second value <em>every time<\/em> it re-appears.<\/p>\n<p>Department 30 appeared very early on in the initial table scan of <code>emp<\/code> so Oracle ran the subquery and cached the department number and average salary for future re-use; a few rows later department 310 appeared for the first time so Oracle ran the subquery and found that it couldn\u2019t cache the result, and the same thing happened two more times before the end of the driving table scan: resulting in two <em>starts<\/em> more than were in the original \u201cperfect\u201d run.<\/p>\n<p>In many cases the difference in performance may not be noticeable, but if you have some queries of this type where performance varies dramatically because of the luck of the caching effect it\u2019s nice to understand what\u2019s happening so that you can choose to work around the problem.<\/p>\n<h2>Pushing subqueries<\/h2>\n<p>Another feature that can affect the amount of work that takes place when a query involves a subquery is the timing of the subquery. In some cases, you may find that you can reduce the number of <em>starts<\/em> of the subquery by overriding the optimizer\u2019s decision of where in the plan the subquery should appear; in others you may find that you can eliminate more data at an earlier point in the query and do less work overall even if the number of starts of the subquery doesn\u2019t change.<\/p>\n<p>Adding a third table from the <code>scott<\/code> schema, here\u2019s a query that tries to identify employees of a particular salary grade by referencing the <code>salgrade<\/code> table in a subquery:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">select  \/*+\r\n                qb_name(main)\r\n                gather_plan_statistics\r\n        *\/\r\n        e.ename, e.sal, d.dname\r\nfrom\r\n        emp e, dept d\r\nwhere\r\n        d.deptno = e.deptno\r\nand     exists  (\r\n                select  \/*+ qb_name(subq) no_unnest *\/\r\n                        null\r\n                from    salgrade s\r\n                where   s.grade = 5\r\n                and     e.sal between s.losal and s.hisal\r\n        )\r\n\/<\/pre>\n<p>By default, Oracle has a strong tendency to run filter subqueries at the latest possible stage in the execution plan, and that\u2019s exactly how this query behaves. Here\u2019s the plan, with the rowsource execution stats, query block names and predicate information:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"883\" height=\"387\" class=\"wp-image-93564\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-18.png\" \/><\/p>\n<p>Operation 1 is the <em>FILTER<\/em> operation that says: <em>\u201cfor each row supplied by the hash join at operation 2, start the subquery at operation 5\u201d<\/em>. Since this is a plan pulled from memory the filter predicate for operation 1 has \u201clost\u201d the subquery text, of course. You can see, though, from the Query Block Name section that the original query blocks still exist, the optimizer has not transformed our subquery out of existence.<\/p>\n<p>The whole of the <code>emp<\/code> data set joins to the matching <code>dept<\/code> rows before attempting to discard any data. Maybe it would be more efficient to get Oracle to test each <code>emp<\/code> row before attempting the join to <code>dept<\/code>. The mechanism for doing this is known as \u201csubquery pushing\u201d, and you can use the <code>push_subq<\/code> hint to force it to happen (or <code>no_push_subq<\/code> if it\u2019s happening when you don\u2019t want it to happen).<\/p>\n<p>There are two options for the hint. It can either be put into the subquery that you want to run at the earliest possible moment, or it can go in the main query block but with the <code>@queryblock<\/code> parameter to tell Oracle which (sub)query block you want pushed. In this case I\u2019ll add the hint <code>\/*+ push_subq(@subq) *\/<\/code> to the main query block, producing the following plan:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"938\" height=\"409\" class=\"wp-image-93565\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2022\/03\/word-image-19.png\" \/><\/p>\n<p>There are three important points on display here. First, although the amount of work has hardly changed and the number of starts of the subquery has not changed at all, you can see (operation 3, A-rows=1) that the number of <code>emp<\/code> rows that go into the join was reduced from 12 to just 1 which, essentially, is why the workload for this query has gone down. Secondly, because the cardinality (Rows) estimate for <code>emp<\/code> has changed so much, the optimizer has changed the join method. Finally (and the most important generic point when reading execution plans) the filter operation has disappeared, re-appearing only as a filter predicate at operation 3 although (because the plan was pulled from memory) the subquery text itself has, as in the previous example, been \u201clost\u201d. In cases like this, if you\u2019re only using the basic <em>\u201cfirst child first \u2013move down and right\u201d<\/em> strategy for reading a plan you\u2019ll misinterpret the plan.<\/p>\n<p>The Query Block Name information shows that operation 4 represents the query block called <code>subq<\/code><strong><em>. <\/em><\/strong>That\u2019s the big clue showing that you need to think first about the nested loop join that is operations 1,2,3,5 and 6 (labelled <code>MAIN<\/code>) \u2013 and then worry about how to stitch the subquery that is operation 4 into that nested loop. When putting the subquery to one side and then bringing it back for later consideration, it\u2019s easier to see that you have a nested loop from <code>emp<\/code> to <code>dept<\/code> but run the filter subquery against each <code>emp<\/code> row before joining to <code>dept<\/code>.<\/p>\n<p>It\u2019s worth noting that in this example there is a correlated subquery acting as a filter predicate against a table access operation, it is also possible for a correlated subquery to act as a filter predicate against an index operation.<\/p>\n<h2>Summary<\/h2>\n<p>This article discussed two topics \u2013 <strong><em>scalar subquery caching<\/em><\/strong> and <strong><em>subquery pushing<\/em><\/strong>. Technically, of course, neither of these is a \u201ctransformation\u201d in that they don\u2019t manipulate your original text to re-engineer the query blocks that need to be optimized; one of them is a built-in run-time optimization, and the other doesn\u2019t change the query block that you\u2019re \u201cpushing\u201d (or not) \u2013 even though the rest of the plan may change as a consequence of your choice.<\/p>\n<p>The number of times a subquery starts can make a huge difference to the workload \u2013 you might want to subvert the optimizer\u2019s choice because you have a better idea of how well the caching might work; conversely if you recognize that the caching benefit shows extreme variation for a critical query you might rewrite the query to work completely differently, or you might engineer a solution that guarantees you get the maximum possible benefit from the caching mechanism.<\/p>\n<p>The point in a plan where a filter subquery runs can make a significant difference to the number of times it runs or to the amount of data that passes through the rest of the plan. In either case, the impact on the total workload could be enormous. When a subquery is \u201cpushed\u201d to run as early as possible (whether that\u2019s by default or due to hinting) the explicit <code>FILTER<\/code> operation disappears from the plan, and the shape of the plan changes in a way that can be very misleading if you\u2019re following the <em>\u201cmove down and right\u201d<\/em> method of reading a plan and haven\u2019t checked the Query Block information and Predicate Information.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, Jonathan Lewis discusses why you might want to stop the optimizer from unnesting some subqueries and how to control where the optimizer positions each subquery.&hellip;<\/p>\n","protected":false},"author":101205,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[53,143533],"tags":[95506],"coauthors":[39048],"class_list":["post-93558","post","type-post","status-publish","format-standard","hentry","category-featured","category-oracle-databases","tag-automate"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/93558","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/101205"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=93558"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/93558\/revisions"}],"predecessor-version":[{"id":93569,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/93558\/revisions\/93569"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=93558"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=93558"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=93558"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=93558"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}