Let's see this plan selection process in action. First, we create a SQL plan baseline by enabling automatic plan capture and executing the query twice:
SQL> alter session set optimizer_capture_sql_plan_baselines = true; Session altered. SQL> var pid number SQL> exec :pid := 100; PL/SQL procedure successfully completed. SQL> select p.prod_name, s.amount_sold, t.calendar_year 2 from sales s, products p, times t 3 where s.prod_id = p.prod_id 4 and s.time_id = t.time_id 5 and p.prod_id < :pid; PROD_NAME AMOUNT_SOLD CALENDAR_YEAR --------- ----------- ------------- ... 9 rows selected. SQL> select * from table(dbms_xplan.display_cursor('bfbr3zrg9d5cc', 0, 'basic note')); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------- EXPLAINED SQL STATEMENT: ------------------------ select p.prod_name, s.amount_sold, t.calendar_year from sales s, products p, times t where s.prod_id = p.prod_id and s.time_id = t.time_id and p.prod_id < :pid Plan hash value: 2787970893 ---------------------------------------------------------------- | Id | Operation | Name | ---------------------------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | NESTED LOOPS | | | 2 | NESTED LOOPS | | | 3 | HASH JOIN | | | 4 | TABLE ACCESS BY INDEX ROWID | PRODUCTS | | 5 | INDEX RANGE SCAN | PRODUCTS_PK | | 6 | PARTITION RANGE ALL | | | 7 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES | | 8 | BITMAP CONVERSION TO ROWIDS | | | 9 | BITMAP INDEX RANGE SCAN | SALES_PROD_BIX | | 10 | INDEX UNIQUE SCAN | TIME_PK | | 11 | TABLE ACCESS BY INDEX ROWID | TIMES | ---------------------------------------------------------------- 25 rows selected. SQL> select p.prod_name, s.amount_sold, t.calendar_year 2 from sales s, products p, times t 3 where s.prod_id = p.prod_id 4 and s.time_id = t.time_id 5 and p.prod_id < :pid; PROD_NAME AMOUNT_SOLD CALENDAR_YEAR --------- ----------- ------------- ... 9 rows selected. SQL> alter session set optimizer_capture_sql_plan_baselines = false; Session altered. SQL> select sql_text, plan_name, enabled, accepted from dba_sql_plan_baselines; SQL_TEXT PLAN_NAME ENA ACC ---------------------------------------- ------------------------------ --- --- select p.prod_name, s.amount_sold, t.cal SYS_SQL_PLAN_fcc170b0a62d0f4d YES YES endar_year from sales s, products p, times t where s.prod_id = p.prod_id and s.time_id = t.time_id and p.prod_id < :pid
We can see that a SQL plan baseline was created for the statement. Suppose the statement is hard parsed again (we do it here by flushing the shared pool). Let's turn off SQL plan management and execute the query with a different bind value:
SQL> exec :pid := 100000; PL/SQL procedure successfully completed. SQL> alter system flush shared_pool; System altered. SQL> alter session set optimizer_use_sql_plan_baselines = false; Session altered. SQL> select p.prod_name, s.amount_sold, t.calendar_year 2 from sales s, products p, times t 3 where s.prod_id = p.prod_id 4 and s.time_id = t.time_id 5 and p.prod_id < :pid; PROD_NAME AMOUNT_SOLD CALENDAR_YEAR --------- ----------- ------------- ... 960 rows selected. SQL> select * from table(dbms_xplan.display_cursor('bfbr3zrg9d5cc', 0, 'basic note')); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------- EXPLAINED SQL STATEMENT: ------------------------ select p.prod_name, s.amount_sold, t.calendar_year from sales s, products p, times t where s.prod_id = p.prod_id and s.time_id = t.time_id and p.prod_id < :pid Plan hash value: 2361178149 ------------------------------------------ | Id | Operation | Name | ------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | HASH JOIN | | | 2 | HASH JOIN | | | 3 | PARTITION RANGE ALL| | | 4 | TABLE ACCESS FULL | SALES | | 5 | TABLE ACCESS FULL | TIMES | | 6 | TABLE ACCESS FULL | PRODUCTS | ------------------------------------------
We can see that the optimizer selected a different plan because the new bind value makes the predicate less selective. Let's turn SQL plan management back on and re-execute the query with the same bind value:
SQL> alter session set optimizer_use_sql_plan_baselines = true; Session altered. SQL> select p.prod_name, s.amount_sold, t.calendar_year 2 from sales s, products p, times t 3 where s.prod_id = p.prod_id 4 and s.time_id = t.time_id 5 and p.prod_id < :pid; PROD_NAME AMOUNT_SOLD CALENDAR_YEAR --------- ----------- ------------- ... 960 rows selected. SQL> select * from table(dbms_xplan.display_cursor('bfbr3zrg9d5cc', 0, 'basic note')); PLAN_TABLE_OUTPUT ------------------------------------------------------------------------------- EXPLAINED SQL STATEMENT: ------------------------ select p.prod_name, s.amount_sold, t.calendar_year from sales s, products p, times t where s.prod_id = p.prod_id and s.time_id = t.time_id and p.prod_id < :pid Plan hash value: 2787970893 ---------------------------------------------------------------- | Id | Operation | Name | ---------------------------------------------------------------- | 0 | SELECT STATEMENT | | | 1 | NESTED LOOPS | | | 2 | NESTED LOOPS | | | 3 | HASH JOIN | | | 4 | TABLE ACCESS BY INDEX ROWID | PRODUCTS | | 5 | INDEX RANGE SCAN | PRODUCTS_PK | | 6 | PARTITION RANGE ALL | | | 7 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES | | 8 | BITMAP CONVERSION TO ROWIDS | | | 9 | BITMAP INDEX RANGE SCAN | SALES_PROD_BIX | | 10 | INDEX UNIQUE SCAN | TIME_PK | | 11 | TABLE ACCESS BY INDEX ROWID | TIMES | ---------------------------------------------------------------- Note ----- - SQL plan baseline SYS_SQL_PLAN_fcc170b0a62d0f4d used for this statement
The note at the bottom tells you that the optimizer is using the SQL plan baseline. In other words, we can see that the optimizer used an accepted plan in the SQL plan baseline in favor of a new plan. In fact, we can also check that the optimizer inserted the new plan into the statement's plan history:
SQL> select sql_text, plan_name, enabled, accepted from dba_sql_plan_baselines; SQL_TEXT PLAN_NAME ENA ACC ---------------------------------------- ------------------------------ --- --- select p.prod_name, s.amount_sold, t.cal SYS_SQL_PLAN_fcc170b08cbcb825 YES NO endar_year from sales s, products p, times t where s.prod_id = p.prod_id and s.time_id = t.time_id and p.prod_id < :pid select p.prod_name, s.amount_sold, t.cal SYS_SQL_PLAN_fcc170b0a62d0f4d YES YES endar_year from sales s, products p, times t where s.prod_id = p.prod_id and s.time_id = t.time_id and p.prod_id < :pid
The 'NO' value for the accepted column implies that the new plan is in the plan history but is not available for use until it is verified to be a good plan. The optimizer will continue to use an accepted plan until new plans are verified and added to the SQL plan baseline. If there is more than one plan in the SQL plan baseline, the optimizer will use the one with the best cost under the then-current conditions (statistics, bind values, parameter settings and so on).
When you create a SQL plan baseline for a SQL statement, the SPM aware optimizer thus guarantees that no new plans will be used other than the ones in the SQL plan baseline. This prevents unexpected plan changes that sometimes lead to performance regressions.
Preventing new plans from being used is fine, but what if the new plans are in fact better? In Part 3, we will describe how new and improved plans are added to a SQL plan baseline.
No comments:
Post a Comment