{
  "date": "2016-06-21T17:44:04",
  "slug": "adaptive-plans-and-sql-baselines",
  "link": "https://www.dbi-services.com/blog/adaptive-plans-and-sql-baselines/",
  "title": {
    "rendered": "Adaptive Plans and SQL Baselines"
  },
  "content": {
    "rendered": "<h2>By Franck Pachot</h2>\n<p>.<br />\nI encountered recently an issue with Adaptive Plan and SPM. Documentations says that it works perfectly together but I remembered a presentation from Nelson Calero at UKOUG TECH15 mentioning strange behavior. I reproduced the issue and share the test case here as you may encounter it in 12.1 leading to regressions when you capture SQL Plan Baselines.<br />\n<!--more--></p>\n<h3>Cleanup</h3>\n<p>Whith all those adaptive features, you need to start clean if you want a reproductible testcase</p>\n<pre><code>SQL&gt; -- drop tables\nSQL&gt; drop table DEMO1;\nTable dropped.\nSQL&gt; drop table DEMO2;\nTable dropped.\nSQL&gt; \nSQL&gt; whenever sqlerror exit failure\nSQL&gt; \nSQL&gt; -- drop all sql plan baselines\nSQL&gt; set serveroutput on long 100000 longc 100000\nSQL&gt; exec for i in (select sql_handle, plan_name, accepted, origin, created from dba_sql_plan_baselines) loop dbms_output.put_line(''||dbms_spm.drop_sql_plan_baseline(sql_handle=&gt;i.sql_handle,plan_name=&gt;i.plan_name)); end loop;\n1\n&nbsp;\nPL/SQL procedure successfully completed.\n&nbsp;\nSQL&gt; set serveroutput off\nSQL&gt; select 0/0 from dba_sql_plan_baselines;\nno rows selected\nSQL&gt; \nSQL&gt; -- flush shared pool\nSQL&gt; alter system flush shared_pool;\nSystem altered.\nSQL&gt; select 0/0 from v$sql where sql_id='agw7bn072730a';\nno rows selected</code></pre>\n<h3>Create the tables</h3>\n<pre><code>SQL&gt; -- create two tables with few rows for L=1 and lot of rows for L=15\nSQL&gt; create table DEMO2 (id constraint PK1 primary key,l) as select rownum,floor(log(2,rownum)) from xmltable('1 to 100000');\nTable created.\n&nbsp;\nSQL&gt; create table DEMO1 as select * from DEMO2;\nTable created.</code></pre>\n<h3>Run the query</h3>\n<pre><code>\nSQL&gt; -- run a join for the few rows case\nSQL&gt; alter session set statistics_level=all;\nSession altered.\nSQL&gt; select count(DEMO2.l) from DEMO1 join DEMO2 using(id) where DEMO1.l=1;\n&nbsp;\nCOUNT(DEMO2.L)\n--------------\n             3</code></pre>\n<p>And here is the adaptive plan:</p>\n<pre><code>SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,format=&gt;'allstats last +adaptive +outline'));\n&nbsp;\nPLAN_TABLE_OUTPUT\n--------------------------------------------------------------------------------------------------------------\nSQL_ID  agw7bn072730a, child number 0\n-------------------------------------\nselect count(DEMO2.l) from DEMO1 join DEMO2 using(id) where DEMO1.l=1\n&nbsp;\nPlan hash value: 2870612662\n&nbsp;\n-------------------------------------------------------------------------------------------------------------\n|   Id  | Operation                      | Name  | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |\n-------------------------------------------------------------------------------------------------------------\n|     0 | SELECT STATEMENT               |       |      1 |        |      1 |00:00:00.01 |     203 |    183 |\n|     1 |  SORT AGGREGATE                |       |      1 |      1 |      1 |00:00:00.01 |     203 |    183 |\n|- *  2 |   HASH JOIN                    |       |      1 |   5882 |      3 |00:00:00.01 |     203 |    183 |\n|     3 |    NESTED LOOPS                |       |      1 |   5882 |      3 |00:00:00.01 |     203 |    183 |\n|     4 |     NESTED LOOPS               |       |      1 |        |      3 |00:00:00.01 |     200 |    183 |\n|-    5 |      STATISTICS COLLECTOR      |       |      1 |        |      3 |00:00:00.01 |     195 |    179 |\n|  *  6 |       TABLE ACCESS FULL        | DEMO1 |      1 |   5882 |      3 |00:00:00.01 |     195 |    179 |\n|  *  7 |      INDEX UNIQUE SCAN         | PK1   |      3 |        |      3 |00:00:00.01 |       5 |      4 |\n|     8 |     TABLE ACCESS BY INDEX ROWID| DEMO2 |      3 |      1 |      3 |00:00:00.01 |       3 |      0 |\n|-    9 |    TABLE ACCESS FULL           | DEMO2 |      0 |    100K|      0 |00:00:00.01 |       0 |      0 |\n-------------------------------------------------------------------------------------------------------------\n&nbsp;\nOutline Data\n-------------\n&nbsp;\n  /*+\n      BEGIN_OUTLINE_DATA\n      INDEX(@\"SEL$58A6D7F6\" \"DEMO2\"@\"SEL$1\" (\"DEMO2\".\"ID\"))\n      NLJ_BATCHING(@\"SEL$58A6D7F6\" \"DEMO2\"@\"SEL$1\")\n      USE_NL(@\"SEL$58A6D7F6\" \"DEMO2\"@\"SEL$1\")\n      IGNORE_OPTIM_EMBEDDED_HINTS\n      OPTIMIZER_FEATURES_ENABLE('12.2.0.1')\n      DB_VERSION('12.2.0.1')\n      ALL_ROWS\n      OUTLINE_LEAF(@\"SEL$58A6D7F6\")\n      MERGE(@\"SEL$1\")\n      OUTLINE(@\"SEL$2\")\n      OUTLINE(@\"SEL$1\")\n      FULL(@\"SEL$58A6D7F6\" \"DEMO1\"@\"SEL$1\")\n      LEADING(@\"SEL$58A6D7F6\" \"DEMO1\"@\"SEL$1\" \"DEMO2\"@\"SEL$1\")\n      END_OUTLINE_DATA\n  */\n&nbsp;\nPredicate Information (identified by operation id):\n---------------------------------------------------\n&nbsp;\n   2 - access(\"DEMO1\".\"ID\"=\"DEMO2\".\"ID\")\n   6 - filter(\"DEMO1\".\"L\"=1)\n   7 - access(\"DEMO1\".\"ID\"=\"DEMO2\".\"ID\")\n&nbsp;\nNote\n-----\n   - this is an adaptive plan (rows marked '-' are inactive)\n</code></pre>\n<p>It&#8217;s an adaptive plan, HASH JOIN was the initial choice but first execution activated the NESTED LOOP.</p>\n<h3>SQL Baseline Capture</h3>\n<pre><code>SQL&gt; alter session set optimizer_capture_sql_plan_baselines=true;\nSession altered.\n&nbsp;\nSQL&gt; select count(DEMO2.l) from DEMO1 join DEMO2 using(id) where DEMO1.l=1;\n&nbsp;\nCOUNT(DEMO2.L)\n--------------\n             3\n&nbsp;\nSQL&gt; alter session set optimizer_capture_sql_plan_baselines=false;\nSession altered.</code></pre>\n<p>Here is the SQL Baseline:</p>\n<pre><code>SQL&gt; select sql_handle, plan_name, accepted, origin, created from dba_sql_plan_baselines;\n&nbsp;\nSQL_HANDLE                     PLAN_NAME                                ACC ORIGIN                        CREATED\n------------------------------ ---------------------------------------- --- ----------------------------- ---------------------------------------------------------------------------\nSQL_4c1b404640b73a81           SQL_PLAN_4s6u08t0bffn1e47b6a4d           YES AUTO-CAPTURE                  28-MAY-16 09.13.04.000000 PM</code></pre>\n<p>and its plan:</p>\n<pre><code>SQL&gt; select plan_table_output from dba_sql_plan_baselines,table(dbms_xplan.display_sql_plan_baseline(sql_handle, plan_name, format=&gt;'+adaptive'));\n&nbsp;\nPLAN_TABLE_OUTPUT\n--------------------------------------------------------------------------------\n&nbsp;\n--------------------------------------------------------------------------------\nSQL handle: SQL_4c1b404640b73a81\nSQL text: select count(DEMO2.l) from DEMO1 join DEMO2 using(id) where DEMO1.l=1\n--------------------------------------------------------------------------------\n&nbsp;\n--------------------------------------------------------------------------------\nPlan name: SQL_PLAN_4s6u08t0bffn1e47b6a4d         Plan id: 3833293389\nEnabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE\nPlan rows: From dictionary\n--------------------------------------------------------------------------------\n&nbsp;\nPlan hash value: 740165205\n&nbsp;\n-------------------------------------------------------------------------------\n|   Id  | Operation           | Name  | Rows  | Bytes | Cost (%CPU)| Time     |\n-------------------------------------------------------------------------------\n|     0 | SELECT STATEMENT    |       |     1 |    16 |   108   (2)| 00:00:01 |\n|     1 |  SORT AGGREGATE     |       |     1 |    16 |            |          |\n|  *  2 |   HASH JOIN         |       |  5882 | 94112 |   108   (2)| 00:00:01 |\n|  *  3 |    TABLE ACCESS FULL| DEMO1 |  5882 | 47056 |    54   (2)| 00:00:01 |\n|     4 |    TABLE ACCESS FULL| DEMO2 |   100K|   781K|    54   (2)| 00:00:01 |\n-------------------------------------------------------------------------------\n&nbsp;\nPredicate Information (identified by operation id):\n---------------------------------------------------\n&nbsp;\n   2 - access(\"DEMO1\".\"ID\"=\"DEMO2\".\"ID\")\n   3 - filter(\"DEMO1\".\"L\"=1)\n&nbsp;\nNote\n-----\n   - this is an adaptive plan (rows marked '-' are inactive)</code></pre>\n<p>Unfortunately, the baseline captured only the &#8216;initial&#8217; plan with the HASH JOIN.<br />\nThis is not what is documented in <a href=\"http://www.oracle.com/technetwork/database/bi-datawarehousing/twp-sql-plan-mgmt-12c-1963237.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">Maria Colgan paper</a>:<br />\n<em>SPM plan capture and Adaptive Plans: When automatic plan capture is enabled and a SQL statement that has an adaptive plan is executed, <strong>only the final plan used will be captured in the SQL plan baseline</strong>.</em></p>\n<h3>Run with baseline</h3>\n<pre><code>SQL&gt; alter session set statistics_level=all;\nSession altered.\n&nbsp;\nSQL&gt; select count(DEMO2.l) from DEMO1 join DEMO2 using(id) where DEMO1.l=1;\n&nbsp;\nCOUNT(DEMO2.L)\n--------------\n             3\n&nbsp;\nSQL&gt; select * from table(dbms_xplan.display_cursor(null,null,format=&gt;'allstats last +adaptive'));\n&nbsp;\nPLAN_TABLE_OUTPUT\n------------------------------------------------------------------------------------------------------------------\nSQL_ID  agw7bn072730a, child number 1\n-------------------------------------\nselect count(DEMO2.l) from DEMO1 join DEMO2 using(id) where DEMO1.l=1\n&nbsp;\nPlan hash value: 740165205\n&nbsp;\n------------------------------------------------------------------------------------------------------------------\n| Id  | Operation           | Name  | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |\n------------------------------------------------------------------------------------------------------------------\n|   0 | SELECT STATEMENT    |       |      1 |        |      1 |00:00:00.01 |     390 |       |       |          |\n|   1 |  SORT AGGREGATE     |       |      1 |      1 |      1 |00:00:00.01 |     390 |       |       |          |\n|*  2 |   HASH JOIN         |       |      1 |   5882 |      3 |00:00:00.01 |     390 |  2545K|  2545K|  826K (0)|\n|*  3 |    TABLE ACCESS FULL| DEMO1 |      1 |   5882 |      3 |00:00:00.01 |     195 |       |       |          |\n|   4 |    TABLE ACCESS FULL| DEMO2 |      1 |    100K|    100K|00:00:00.01 |     195 |       |       |          |\n------------------------------------------------------------------------------------------------------------------\n&nbsp;\nPredicate Information (identified by operation id):\n---------------------------------------------------\n&nbsp;\n   2 - access(\"DEMO1\".\"ID\"=\"DEMO2\".\"ID\")\n   3 - filter(\"DEMO1\".\"L\"=1)\n&nbsp;\nNote\n-----\n   - SQL plan baseline SQL_PLAN_4s6u08t0bffn1e47b6a4d used for this statement</code></pre>\n<p>This confirms that the SQL baseline forces the initial HASH JOIN plan. It&#8217;s a bug that should be fixed in 12.2 so for the moment, be very careful when you want to fix an adaptive plan with SQL Baselines: your goal is to stabilize once you have the optimal plan, but the result may be a regression to a bad plan.</p>\n",
    "protected": false
  }
}
