Fake baseline - denis-kol4ev/OraDBA GitHub Wiki

Fake baseline

Техника Fake Baseline заключается в следующем: cоздать желаемый план выполнения для одного запроса и перенести план выполнения на другой запрос, используя процедуру dbms_spm.load_plans_from_cursor_cache

--Предварительный шаг
drop table t1;
alter system flush shared_pool;
--Создадим тестовую таблицу на 1 млн строк, индексы, собираем статистику
create table t1 as
select mod(rownum, 5) c1,
       mod(rownum, 100) c2,
       case mod(rownum, 20)
         when 0 then
          'aaa'
         when 10 then
          'bbb'
         when 5 then
          'ccc'
         else
          dbms_random.string(opt => 'l', len => 3)
       end c3,
       dbms_random.string(opt => 'a', len => 10) c4
  from dual
connect by level <= 1e6;

create index t1_c1_с2_idx on t1(c1,c2);
create index t1_c3_c1_idx on t1(c3,c1);

begin
  dbms_stats.gather_table_stats(user, 'T1');
end;
/
-- Оригинальный план работает по индексу t1_c3_c1_idx, но мы хотим чтобы использовался t1_c1_с2_idx
explain plan for select c4 from t1 t where c1 =:1 and c3 in (:2, :3);
select * from table(dbms_xplan.display);
Plan hash value: 840289599
 
-----------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |              |    23 |   414 |    24   (0)| 00:00:01 |
|   1 |  INLIST ITERATOR                     |              |       |       |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID BATCHED| T1           |    23 |   414 |    24   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN                  | T1_C3_C1_IDX |    23 |       |     4   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   3 - access(("C3"=:2 OR "C3"=:3) AND "C1"=TO_NUMBER(:1))
-- Доработанный план использует требуемый нам индекс t1_c1_с2_idx
explain plan for select /*+ index(t t1_c1_с2_idx) */ c4 from t1 t where c1 =:1 and c3 in (:2, :3);
select * from table(dbms_xplan.display);
Plan hash value: 571715440
 
----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |              |    23 |   414 | 71927   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T1           |    23 |   414 | 71927   (1)| 00:00:03 |
|*  2 |   INDEX RANGE SCAN                  | T1_C1_С2_IDX |   200K|       |   472   (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("C3"=:2 OR "C3"=:3)
   2 - access("C1"=TO_NUMBER(:1))
-- Выполним запросы для получения их sql_id 
begin
  execute immediate 'select c4 from t1 t where c1 =:1 and c3 in (:2, :3)' using 0, 'aaa', 'bbb';
end;
/

begin
  execute immediate 'select /*+ index(t t1_c1_с2_idx) */ c4 from t1 t where c1 =:1 and c3 in (:2, :3)' using 0, 'aaa', 'bbb';
end;
/

select sql_text, sql_id, plan_hash_value, child_number, executions, sql_plan_baseline
  from v$sql s
 where sql_text like '%from t1 t where c1 =:1 and c3 in (:2, :3)%'
   and not regexp_like(sql_text, 'v\$sql|explain|execute', 'i');
SQL_TEXT                       SQL_ID          PLAN_HASH_VALUE CHILD_NUMBER EXECUTIONS SQL_PLAN_BASELINE
------------------------------ --------------- --------------- ------------ ---------- -----------------
select c4 from t1 t where c1 = 5x745mn4sj9m2         840289599            0          1
:1 and c3 in (:2, :3)

select /*+ index(t t1_c1_с2_id 65jdnxycwrs71         571715440            0          1
x) */ c4 from t1 t where c1 =:
1 and c3 in (:2, :3)

Используем sql_id и child_number оригинального запроса для получения текса запроса в clob, затем используем sql_id и plan_hash_value доработанного запроса для того чтобы ассоциировать новый план с оригинальным запросом, сохранив результат в SQL baseline

-- Создаем SQL baseline для оригинального запроса с планом от доработанного
declare
  v_sql_text clob;
  v_plans    pls_integer;
begin
  select sql_fulltext
    into v_sql_text
    from v$sql
   where sql_id = '&original_sql_id'
     and child_number = 0;

  v_plans := dbms_spm.load_plans_from_cursor_cache(sql_id          => '&hinted_sql_id',
                                                   plan_hash_value => '&hinted_plan_hash_value',
                                                   sql_text        => v_sql_text,
                                                   fixed           => 'NO',
                                                   enabled         => 'YES');

  dbms_output.put_line('Plans Loaded: ' || v_plans);

end;
/
Plans Loaded: 1
-- Проверяем что оригинальный запрос использует желаемый план с индексом t1_c1_с2_idx, в Note наличие сообщения об использовании baseline
explain plan for select c4 from t1 t where c1 =:1 and c3 in (:2, :3);
select * from table(dbms_xplan.display);
Plan hash value: 571715440
 
----------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |              |    23 |   414 | 71927   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T1           |    23 |   414 | 71927   (1)| 00:00:03 |
|*  2 |   INDEX RANGE SCAN                  | T1_C1_С2_IDX |   200K|       |   472   (1)| 00:00:01 |
----------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("C3"=:2 OR "C3"=:3)
   2 - access("C1"=TO_NUMBER(:1))
 
Note
-----
   - SQL plan baseline "SQL_PLAN_2p70zf9h31ydg4162c407" used for this statement
-- Проверяем статистику реального выполнения 

alter session set "_rowsource_execution_statistics" = true;

declare
  type c4_tab_type is table of t1.c4%type index by pls_integer;
  v1 c4_tab_type;
begin
  for i in 1 .. 10 loop
    execute immediate 'select c4 from t1 t where c1 =:1 and c3 in (:2, :3)' bulk collect into v1 using 0, 'aaa', 'bbb';
  end loop;
end;
/

select sql_text, sql_id, plan_hash_value, child_number, executions, sql_plan_baseline
  from v$sql s
 where sql_text like '%from t1 t where c1 =:1 and c3 in (:2, :3)%'
   and not regexp_like(sql_text, 'v\$sql|explain|execute', 'i');
SQL_TEXT                                 SQL_ID          PLAN_HASH_VALUE CHILD_NUMBER EXECUTIONS SQL_PLAN_BASELINE
---------------------------------------- --------------- --------------- ------------ ---------- ------------------------------
select c4 from t1 t where c1 =:1 and c3  5x745mn4sj9m2         571715440            0         10 SQL_PLAN_2p70zf9h31ydg4162c407
in (:2, :3)


select /*+ index(t t1_c1_с2_idx) */ c4 f 65jdnxycwrs71         571715440            0          1
rom t1 t where c1 =:1 and c3 in (:2, :3)
select * from table(dbms_xplan.display_cursor('5x745mn4sj9m2',null,'ALL ALLSTATS LAST'));
SQL_ID  5x745mn4sj9m2, child number 0
-------------------------------------
select c4 from t1 t where c1 =:1 and c3 in (:2, :3)
 
Plan hash value: 571715440
 
----------------------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name         | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |
----------------------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |              |      1 |        |       | 71927 (100)|          |    100K|00:00:00.06 |   71888 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| T1           |      1 |     23 |   414 | 71927   (1)| 00:00:03 |    100K|00:00:00.06 |   71888 |
|*  2 |   INDEX RANGE SCAN                  | T1_C1_С2_IDX |      1 |    200K|       |   472   (1)| 00:00:01 |    200K|00:00:00.02 |     448 |
----------------------------------------------------------------------------------------------------------------------------------------------
 
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
 
   1 - SEL$1 / T@SEL$1
   2 - SEL$1 / T@SEL$1
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter(("C3"=:2 OR "C3"=:3))
   2 - access("C1"=:1)
 
Column Projection Information (identified by operation id):
-----------------------------------------------------------
 
   1 - "C4"[VARCHAR2,4000]
   2 - "T".ROWID[ROWID,10]
 
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 6
---------------------------------------------------------------------------
 
   0 -  STATEMENT
           -  ALL_ROWS
           -  DB_VERSION('19.1.0')
           -  IGNORE_OPTIM_EMBEDDED_HINTS
           -  OPTIMIZER_FEATURES_ENABLE('19.1.0')
 
   1 -  SEL$1 / T@SEL$1
           -  BATCH_TABLE_ACCESS_BY_ROWID(@"SEL$1" "T"@"SEL$1")
           -  INDEX_RS_ASC(@"SEL$1" "T"@"SEL$1" ("T1"."C1" "T1"."C2"))
 
Note
-----
   - SQL plan baseline SQL_PLAN_2p70zf9h31ydg4162c407 used for this statement

Дополнительно

--Просмотр baseline
select s.sql_handle,
       s.plan_name,
       s.enabled,
       s.accepted,
       s.fixed,
       s.optimizer_cost,
       s.created,
       s.last_modified,
       s.last_executed,
       s.sql_text
  from dba_sql_plan_baselines s;
-- Просмотр плана из baseline
select s.sql_id, sql_handle -- получаем sql_handle
  from dba_sql_plan_baselines b, v$sql s
 where S. EXACT_MATCHING_SIGNATURE = B.SIGNATURE
   and S.SQL_PLAN_BASELINE = B.PLAN_NAME;

-- Просмотр все планов имеющихся в baseline для определенного sql
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE(sql_handle => 'SQL_2a9c1f726030f9af',format => 'ALL'));

-- Просмотр конкретного плана из baseline
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE(plan_name => 'SQL_PLAN_2p70zf9h31ydg4162c407'));
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE(plan_name => 'SQL_PLAN_2p70zf9h31ydg4162c407', format => 'OUTLINE'));
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE(plan_name => 'SQL_PLAN_2p70zf9h31ydg4162c407', format => 'ADVANCED'));
-- Удаление baseline
declare
 v1 pls_integer;
begin
  v1 := dbms_spm.drop_sql_plan_baseline(sql_handle => 'SQL_2a9c1f726030f9af');
end;
/

Можно ли использовать один baseline для разных запросов?

Можно, если запросы с одинаковой сигнатурой Baseline, profile и sql patch используют "SQL signature":

A numeric hash value computed using a SQL statement text that has been normalized for case insensitivity and white space. It uniquely identifies a SQL statement. The database uses this signature as a key to maintain SQL management objects such as SQL profiles, SQL plan baselines, and SQL patches.

Что не то же самое, что и sql_id, который вычисляется другой хэш функцией. Исходя из определения сигнатуры любая модификация запроса по составу полей и т. д. меняет ее. Поэтому никак нельзя применить baseline или profile сразу к нескольким разным запросам. Но запросы с одинаковой сигнатурой могут использовать один и тот же baseline или profile.

*ACCEPT_SQL_PROFILE с опцией force_match => TRUE

Можно заранее проверить сигнатуру текста запроса:

-- Получить сигнатуру из текста запроса
select dbms_sqltune.sqltext_to_signature ('select * from dual') as signature from dual;

select dbms_sqltune.sqltext_to_signature(sql_text    => 'select с1 from t1 where c2 = 10',
                                         force_match => 1) as signature
  from dual;

select dbms_sqltune.sqltext_to_signature(sql_text    => 'select с1 from t1 where c2 = 100',
                                         force_match => 1) as signature
  from dual;

-- Получить sql_id из текста запроса
select dbms_sql_translator.sql_id('select c4 from t1 t where c1 =:1 and c3 in (:2, :3)') from dual;
⚠️ **GitHub.com Fallback** ⚠️