Disclaimer: after writing this post (but before publishing it) I have seen that other people already blogged about it, so I am ashamed of publishing it anyway… but that’s blogger’s life
Wednesday I have got a nice question after my presentation about Adaptive Features at the DOAG16 conference:
What happens when you load an adaptive plan in a SQL Plan Baseline?
Does it load only the final plan or does it load the whole plan including the inactive operations? Will the plan be evaluated again using the inflection point?
I have decided to do some tests in order to give the best possible answer. I did not spend the time to rethink about producing an adaptive plan. Tim Hall already did an excellent test case to create and alter an adaptive plan in his blog, so I have reused massively most of its code. Thanks Tim :-).
I will not post all the code (please find it in Tim’s post), I will go straight to the plans.
First: I have an adaptive plan that resolves to NESTED LOOPS:
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 4 b.data AS tab2_data 5 FROM tab1 a 6 JOIN tab2 b ON b.tab1_id = a.id 7 WHERE a.code = 'ONE'; ... 30 rows selected. SQL> SET LINESIZE 200 PAGESIZE 100 SQL> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format => 'adaptive')); PLAN_TABLE_OUTPUT ---------------------------------------------------------------------------------------------------------- SQL_ID 4r3harjun4dvz, child number 0 ------------------------------------- SELECT a.data AS tab1_data, b.data AS tab2_data FROM tab1 a JOIN tab2 b ON b.tab1_id = a.id WHERE a.code = 'ONE' Plan hash value: 2672205743 ----------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 3 (100)| | |- * 1 | HASH JOIN | | 25 | 425 | 3 (0)| 00:00:01 | | 2 | NESTED LOOPS | | 25 | 425 | 3 (0)| 00:00:01 | | 3 | NESTED LOOPS | | 25 | 425 | 3 (0)| 00:00:01 | |- 4 | STATISTICS COLLECTOR | | | | | | | 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB1 | 1 | 11 | 2 (0)| 00:00:01 | | * 6 | INDEX RANGE SCAN | TAB1_CODE | 1 | | 1 (0)| 00:00:01 | | * 7 | INDEX RANGE SCAN | TAB2_TAB1_FKI | 25 | | 0 (0)| | | 8 | TABLE ACCESS BY INDEX ROWID | TAB2 | 25 | 150 | 1 (0)| 00:00:01 | |- 9 | TABLE ACCESS FULL | TAB2 | 25 | 150 | 1 (0)| 00:00:01 | ----------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("B"."TAB1_ID"="A"."ID") 6 - access("A"."CODE"='ONE') 7 - access("B"."TAB1_ID"="A"."ID") Note ----- - this is an adaptive plan (rows marked '-' are inactive) 33 rows selected.
Second: I load the plan (lazy way: using baseline capture at session level)
SQL> ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES = TRUE; Session altered. SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 3 b.data AS tab2_data 4 FROM tab1 a 5 JOIN tab2 b ON b.tab1_id = a.id 6 WHERE a.code = 'ONE'; TAB1_DATA TAB2_DATA ---------- ---------- ... 30 rows selected. SQL> r 1 SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 3 b.data AS tab2_data 4 FROM tab1 a 5 JOIN tab2 b ON b.tab1_id = a.id 6* WHERE a.code = 'ONE' TAB1_DATA TAB2_DATA ---------- ---------- ... 30 rows selected. SQL> ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES = FALSE; Session altered. SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines; SQL_HANDLE PLAN_NAME SQL_TEXT ENA ACC FIX -------------------- ---------------------------------- ------------------------------------------ --- --- --- SQL_6c4c6680810dd01a SQL_PLAN_6sm36h20hvn0u55a25f73 SELECT /*+ GATHER_PLAN_STATISTICS */ YES YES NO a.data AS tab1_data, b.data A
Third: re-run the statement and check the plan
SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 3 b.data AS tab2_data 4 FROM tab1 a 5 JOIN tab2 b ON b.tab1_id = a.id 6 WHERE a.code = 'ONE'; TAB1_DATA TAB2_DATA ---------- ---------- ... 30 rows selected. SQL> select * from table(dbms_xplan.display_cursor(format=>'+adaptive')); PLAN_TABLE_OUTPUT ----------------------------------------------------------------------------------------------------- SQL_ID 1km5kczcgr0fr, child number 3 ------------------------------------- SELECT /*+ GATHER_PLAN_STATISTICS */ a.data AS tab1_data, b.data AS tab2_data FROM tab1 a JOIN tab2 b ON b.tab1_id = a.id WHERE a.code = 'ONE' Plan hash value: 2672205743 ------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | | 3 (100)| | | 1 | NESTED LOOPS | | 25 | 425 | 3 (0)| 00:00:01 | | 2 | NESTED LOOPS | | 25 | 425 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB1 | 1 | 11 | 2 (0)| 00:00:01 | |* 4 | INDEX RANGE SCAN | TAB1_CODE | 1 | | 1 (0)| 00:00:01 | |* 5 | INDEX RANGE SCAN | TAB2_TAB1_FKI | 25 | | 0 (0)| | | 6 | TABLE ACCESS BY INDEX ROWID | TAB2 | 25 | 150 | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("A"."CODE"='ONE') 5 - access("B"."TAB1_ID"="A"."ID") Note ----- - SQL plan baseline SQL_PLAN_6sm36h20hvn0u55a25f73 used for this statement 30 rows selected.
It does not look adaptive, but I can also check from the function DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE:
SQL> select * from table (DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE('SQL_6c4c6680810dd01a', format=>'+adaptive')); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- SQL handle: SQL_6c4c6680810dd01a SQL text: SELECT /*+ GATHER_PLAN_STATISTICS */ a.data AS tab1_data, b.data AS tab2_data FROM tab1 a JOIN tab2 b ON b.tab1_id = a.id WHERE a.code = 'ONE' -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Plan name: SQL_PLAN_6sm36h20hvn0u55a25f73 Plan id: 1436704627 Enabled: YES Fixed: NO Accepted: YES Origin: AUTO-CAPTURE Plan rows: From dictionary -------------------------------------------------------------------------------- Plan hash value: 2672205743 --------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 25 | 425 | 3 (0)| 00:00:01 | | 1 | NESTED LOOPS | | 25 | 425 | 3 (0)| 00:00:01 | | 2 | NESTED LOOPS | | 25 | 425 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB1 | 1 | 11 | 2 (0)| 00:00:01 | | * 4 | INDEX RANGE SCAN | TAB1_CODE | 1 | | 1 (0)| 00:00:01 | | * 5 | INDEX RANGE SCAN | TAB2_TAB1_FKI | 25 | | 0 (0)| 00:00:01 | | 6 | TABLE ACCESS BY INDEX ROWID | TAB2 | 25 | 150 | 1 (0)| 00:00:01 | --------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("A"."CODE"='ONE') 5 - access("B"."TAB1_ID"="A"."ID") Note ----- - this is an adaptive plan (rows marked '-' are inactive) 37 rows selected.
Again, despite in the Note section it says it is adaptive, it does not look like an adaptive plan.
Can I trust this information? Of course I did not and tried to check the plan with and without baseline after changing the rows to force a plan switch to HJ (again taking Tim’s example):
SQL> INSERT /*+ APPEND */ INTO tab1 2 SELECT tab1_seq.nextval, 2 'ONE', 4 level 5 FROM dual 6 CONNECT BY level <= 10000; 10000 rows created. SQL> COMMIT; Commit complete. SQL> alter session set optimizer_use_sql_plan_baselines=false; Session altered. SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 3 b.data AS tab2_data 4 FROM tab1 a 5 JOIN tab2 b ON b.tab1_id = a.id 6 WHERE a.code = 'ONE'; TAB1_DATA TAB2_DATA ---------- ---------- ... 30 rows selected. SQL> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format => 'allstats last adaptive')); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------------------------------------------------------ SQL_ID 1km5kczcgr0fr, child number 0 ------------------------------------- SELECT /*+ GATHER_PLAN_STATISTICS */ a.data AS tab1_data, b.data AS tab2_data FROM tab1 a JOIN tab2 b ON b.tab1_id = a.id WHERE a.code = 'ONE' Plan hash value: 1599395313 ------------------------------------------------------------------------------------------------------------------------------------------------ | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | ------------------------------------------------------------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1 | | 30 |00:00:00.01 | 142 | | | | | * 1 | HASH JOIN | | 1 | 25 | 30 |00:00:00.01 | 142 | 2261K| 2261K| 2180K (0)| |- 2 | NESTED LOOPS | | 1 | 25 | 20001 |00:00:00.01 | 124 | | | | |- 3 | NESTED LOOPS | | 1 | 25 | 20001 |00:00:00.01 | 124 | | | | |- 4 | STATISTICS COLLECTOR | | 1 | | 20001 |00:00:00.01 | 124 | | | | | 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB1 | 1 | 1 | 20001 |00:00:00.01 | 124 | | | | | * 6 | INDEX RANGE SCAN | TAB1_CODE | 1 | 1 | 20001 |00:00:00.01 | 74 | | | | |- * 7 | INDEX RANGE SCAN | TAB2_TAB1_FKI | 0 | 25 | 0 |00:00:00.01 | 0 | | | | |- 8 | TABLE ACCESS BY INDEX ROWID | TAB2 | 0 | 25 | 0 |00:00:00.01 | 0 | | | | | 9 | TABLE ACCESS FULL | TAB2 | 1 | 25 | 100 |00:00:00.01 | 18 | | | | ------------------------------------------------------------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("B"."TAB1_ID"="A"."ID") 6 - access("A"."CODE"='ONE') 7 - access("B"."TAB1_ID"="A"."ID") Note ----- - this is an adaptive plan (rows marked '-' are inactive) 34 rows selected. SQL> alter session set optimizer_use_sql_plan_baselines=true; Session altered. SQL> SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 3 b.data AS tab2_data 4 FROM tab1 a 5 JOIN tab2 b ON b.tab1_id = a.id 6 WHERE a.code = 'ONE'; TAB1_DATA TAB2_DATA ---------- ---------- ... 30 rows selected. SQL> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor(format => 'allstats last adaptive')); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- SQL_ID 1km5kczcgr0fr, child number 2 ------------------------------------- SELECT /*+ GATHER_PLAN_STATISTICS */ a.data AS tab1_data, b.data AS tab2_data FROM tab1 a JOIN tab2 b ON b.tab1_id = a.id WHERE a.code = 'ONE' Plan hash value: 2672205743 ----------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | ----------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 30 |00:00:00.01 | 30889 | | 1 | NESTED LOOPS | | 1 | 25 | 30 |00:00:00.01 | 30889 | | 2 | NESTED LOOPS | | 1 | 25 | 30 |00:00:00.01 | 30886 | | 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB1 | 1 | 1 | 20001 |00:00:00.01 | 125 | |* 4 | INDEX RANGE SCAN | TAB1_CODE | 1 | 1 | 20001 |00:00:00.01 | 75 | |* 5 | INDEX RANGE SCAN | TAB2_TAB1_FKI | 20001 | 25 | 30 |00:00:00.02 | 30761 | | 6 | TABLE ACCESS BY INDEX ROWID | TAB2 | 30 | 25 | 30 |00:00:00.01 | 3 | ----------------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("A"."CODE"='ONE') 5 - access("B"."TAB1_ID"="A"."ID") Note ----- - SQL plan baseline SQL_PLAN_6sm36h20hvn0u55a25f73 used for this statement 30 rows selected.
After changing the rows:
- when I do not use the baseline, the plan resolves to HASH JOIN
- when I use it, the baseline forces to NESTED LOOPS.
So the plan in the baseline is not adaptive and it forces to what has been loaded. Is it the final plan or the original one? I have to capture it again to see if a new baseline appears:
SQL> ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES = TRUE; Session altered. SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 3 b.data AS tab2_data 4 FROM tab1 a 5 JOIN tab2 b ON b.tab1_id = a.id 6 WHERE a.code = 'ONE'; TAB1_DATA TAB2_DATA ---------- ---------- ... 30 rows selected. SQL> r 1 SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 3 b.data AS tab2_data 4 FROM tab1 a 5 JOIN tab2 b ON b.tab1_id = a.id 6* WHERE a.code = 'ONE' TAB1_DATA TAB2_DATA ---------- ---------- ... 30 rows selected. SQL> ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES = FALSE; Session altered. SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines; SQL_HANDLE PLAN_NAME SQL_TEXT ENABLED ACCEPTED FIXED -------------------- ------------------------------ -------------------------------------------------------------------------------- --------- --------- --------- SQL_6c4c6680810dd01a SQL_PLAN_6sm36h20hvn0u55a25f73 SELECT /*+ GATHER_PLAN_STATISTICS */ YES YES NO a.data AS tab1_data, b.data A
A new baseline does not appear, so it looks that the original plan is considered by the capture process and not the resolved one! To be 100% sure, let’s try to drop the existing one and redo the test:
SQL> connect / as sysdba Connected SQL> DECLARE 2 v_dropped_plans number; 3 BEGIN 4 v_dropped_plans := DBMS_SPM.DROP_SQL_PLAN_BASELINE ( 5 sql_handle => 'SQL_6c4c6680810dd01a' 6 ); 7 END; 8 / PL/SQL procedure successfully completed. SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines; no rows selected SQL> conn ludo/ludo Connected. SQL> ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES = TRUE; Session altered. SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 3 b.data AS tab2_data 4 FROM tab1 a 5 JOIN tab2 b ON b.tab1_id = a.id 6 WHERE a.code = 'ONE'; TAB1_DATA TAB2_DATA ---------- ---------- ... 30 rows selected. SQL> r 1 SELECT /*+ GATHER_PLAN_STATISTICS */ 2 a.data AS tab1_data, 3 b.data AS tab2_data 4 FROM tab1 a 5 JOIN tab2 b ON b.tab1_id = a.id 6* WHERE a.code = 'ONE' TAB1_DATA TAB2_DATA ---------- ---------- ... 30 rows selected. SQL> ALTER SESSION SET OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES = FALSE; Session altered. SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines; SQL_HANDLE PLAN_NAME SQL_TEXT ENABLED ACCEPTED FIXED -------------------- ------------------------------ -------------------------------------------------------------------------------- --------- --------- --------- SQL_6c4c6680810dd01a SQL_PLAN_6sm36h20hvn0u55a25f73 SELECT /*+ GATHER_PLAN_STATISTICS */ YES YES NO a.data AS tab1_data, b.data A SQL> select * from table (DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE('SQL_6c4c6680810dd01a', format=>'+adaptive')); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- SQL handle: SQL_6c4c6680810dd01a SQL text: SELECT /*+ GATHER_PLAN_STATISTICS */ a.data AS tab1_data, b.data AS tab2_data FROM tab1 a JOIN tab2 b ON b.tab1_id = a.id WHERE a.code = 'ONE' -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- Plan name: SQL_PLAN_6sm36h20hvn0u55a25f73 Plan id: 1436704627 Enabled: YES Fixed: NO Accepted: YES Origin: AUTO-CAPTURE Plan rows: From dictionary -------------------------------------------------------------------------------- Plan hash value: 2672205743 --------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 25 | 425 | 3 (0)| 00:00:01 | | 1 | NESTED LOOPS | | 25 | 425 | 3 (0)| 00:00:01 | | 2 | NESTED LOOPS | | 25 | 425 | 3 (0)| 00:00:01 | | 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB1 | 1 | 11 | 2 (0)| 00:00:01 | | * 4 | INDEX RANGE SCAN | TAB1_CODE | 1 | | 1 (0)| 00:00:01 | | * 5 | INDEX RANGE SCAN | TAB2_TAB1_FKI | 25 | | 0 (0)| 00:00:01 | | 6 | TABLE ACCESS BY INDEX ROWID | TAB2 | 25 | 150 | 1 (0)| 00:00:01 | --------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("A"."CODE"='ONE') 5 - access("B"."TAB1_ID"="A"."ID") Note ----- - this is an adaptive plan (rows marked '-' are inactive) 37 rows selected.
So, despite the fact that I have an adaptive plan that switches from NL to HJ, only the NESTED LOOPS operations are captured in the baseline, I can infer the only the original plan is loaded as SQL Plan Baseline.
References: