Freitag, Juli 02, 2010

SQL_PLAN_BASELINE

Eigentlich wollte ich seit einiger Zeit etwas über Chrsitian Antogninis hochinteressantes Buch Troubleshooting Oracle Performance schreiben, in dem ich schon seit geraumer Zeit lese, aber irgendwie komme ich nicht dazu. Stattdessen kam mir bei der Lektüre in seinem Blog die Idee, mal einen Blick auf das Thema SQL Plan Management in Oracle 11 zu werfen. Laut Oracle-Doku dient das Plan Management zu Folgendem:
SQL plan management is a preventative mechanism that records and evaluates the execution plans of SQL statements over time, and builds SQL plan baselines composed of a set of existing plans known to be efficient. The SQL plan baselines are then used to preserve performance of corresponding SQL statements, regardless of changes occurring in the system.
Man kann also für ein Statement einen offiziell abgesegneten Plan festlegen, der für alle folgenden Ausführungen der Query maßgeblich ist. Wenn ein potentiell besserer Plan berechnet wird, dann wird er von der Datenbank gespeichert, aber erst nach expliziter Bestätigung durch den DBA für weitere Ausführungen verwendet. Bei Tim Hall findet sich eine instruktive Einführung in das Thema.

Trotzdem folgt hier ein kurzer Versuch: Zunächst lege ich eine Testtabelle an:

-- Session 1 
create table test_baseline(col1 not null, col2, col3)
as
select rownum col1
     , lpad(' ', 200, '*') col2
     , lpad(' ', 200, '*') col3
  from dual
connect by level <= 1000000

In einer zweiten Session aktiviere ich jetzt das Erfassen von Plan Baselines, zähle die Sätze meiner Testtabelle und deaktiviere die Planerfassung wieder:

-- Session 2 
ALTER SESSION SET optimizer_capture_sql_plan_baselines = TRUE;
SELECT /* baseline_test */ COUNT(*) FROM test_baseline;
ALTER SESSION SET optimizer_capture_sql_plan_baselines = FALSE;

Der Zugriff kann natürlich nur über FTS erfolgen, da noch kein Index für die Tabelle existiert:

SQL> SELECT /* test */ COUNT(*) FROM test_baseline;

Abgelaufen: 00:00:00.90

Ausführungsplan
----------------------------------------------------------
Plan hash value: 2118400422

----------------------------------------------------------------------------
| Id  | Operation          | Name          | Rows  | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |               |     1 | 10800   (1)| 00:02:32 |
|   1 |  SORT AGGREGATE    |               |     1 |            |          |
|   2 |   TABLE ACCESS FULL| TEST_BASELINE |   814K| 10800   (1)| 00:02:32 |
----------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for this statement
   - SQL plan baseline "SYS_SQL_PLAN_8713193eb8066074" used for this statement


Statistiken
----------------------------------------------------------
          5  recursive calls
          0  db block gets
      28644  consistent gets
      28572  physical reads
          0  redo size
        342  bytes sent via SQL*Net to client
        338  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

Den fehlenden Index lege ich nun in Session 1 an:

SQL> create index test_baseline_idx on test_baseline(col1);

Index wurde erstellt.

Abgelaufen: 00:00:07.45
SQL> SELECT /* test */ COUNT(*) FROM test_baseline;

Abgelaufen: 00:00:00.93

Ausführungsplan
----------------------------------------------------------
Plan hash value: 2118400422

----------------------------------------------------------------------------
| Id  | Operation          | Name          | Rows  | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |               |     1 | 10830   (1)| 00:02:32 |
|   1 |  SORT AGGREGATE    |               |     1 |            |          |
|   2 |   TABLE ACCESS FULL| TEST_BASELINE |  4701K| 10830   (1)| 00:02:32 |
----------------------------------------------------------------------------

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_8713193eb8066074" used for this statement


Statistiken
----------------------------------------------------------
        112  recursive calls
         23  db block gets
      28684  consistent gets
      28726  physical reads
       4592  redo size
        342  bytes sent via SQL*Net to client
        338  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
         11  sorts (memory)
          0  sorts (disk)
          1  rows processed

Der Index wird vom cbo offenbar kalt lächelnd ignoriert, aber immerhin liefert autotrace auch die Begründung dafür: SQL plan baseline "SYS_SQL_PLAN_8713193eb8066074" used for this statement.

Details zur Baseline liefert die View DBA_SQL_PLAN_BASELINES:

select *
  from DBA_SQL_PLAN_BASELINES
 where PLAN_NAME = 'SYS_SQL_PLAN_8713193eb8066074';

-- hier nur die Spalte SQL_HANDLE
SQL_HANDLE
------------------------
SYS_SQL_0c6637d282f8ddb7

Mit Hilfe der Prozedur evolve_sql_plan_baseline aus dem Package dbms_spm kann ich dann prüfen, ob eine geeigneterer Plan zum Statement existiert und diesen gegebenenfalls akzeptieren:
If interrogated by the user (parameter verify = 'YES'), the execution performance of each non-accepted plan is compared against the performance of a plan chosen from the associated SQL plan baseline. If the non-accepted plan performance is found to be better than SQL plan baseline performance, the non-accepted plan is changed to an accepted plan provided such action is permitted by the user (parameter commit = 'YES').
Der Vergleich der Pläne fällt (natürlich) zugunsten des indizierten Zugriffs aus:

SQL> r
  1  SELECT dbms_spm.evolve_sql_plan_baseline(
  2                sql_handle => 'SYS_SQL_9f943b5f8713193e',
  3                plan_name  => '',
  4                time_limit => 10,
  5                verify     => 'yes',
  6                commit     => 'yes'
  7              )
  8*   FROM dual

DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE(SQL_HANDLE=>'SYS_SQL_9F943B5F8713193E',PLAN_NA
--------------------------------------------------------------------------------

-------------------------------------------------------------------------------
                        Evolve SQL Plan Baseline Report
-------------------------------------------------------------------------------

Inputs:
-------
  SQL_HANDLE = SYS_SQL_9f943b5f8713193e
  PLAN_NAME  =
  TIME_LIMIT = 10
  VERIFY     = yes
  COMMIT     = yes

Plan: SYS_SQL_PLAN_8713193efefc82f9
-----------------------------------
  Plan was verified: Time used 1,77 seconds.
  Passed performance criterion: Compound improvement ratio >= 25,65
  Plan was changed to an accepted plan.

                      Baseline Plan      Test Plan     Improv. Ratio
                      -------------      ---------     -------------
  Execution Status:        COMPLETE       COMPLETE
  Rows Processed:                 1              1
  Elapsed Time(ms):            1469            104             14,13
  CPU Time(ms):                1456            103             14,14
  Buffer Gets:                28587           1110             25,75
  Disk Reads:                 27260           1099              24,8
  Direct Writes:                  0              0
  Fetches:                      608             32                19
  Executions:                     1              1

-------------------------------------------------------------------------------
                                 Report Summary
-------------------------------------------------------------------------------
Number of SQL plan baselines verified: 1.
Number of SQL plan baselines evolved: 1.

Offenbar gibt's da eine Verbesserungsmöglichkeit, weil der neue Plan (mit Index-Zugriff) effizienter ist, so dass der neue Plan akzeptiert wird.

SQL> SELECT * 
       FROM table(dbms_xplan.display_sql_plan_baseline
                             ('SYS_SQL_0c6637d282f8ddb7', NULL, 'basic'));

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------

--------------------------------------------------------------------------------
SQL handle: SYS_SQL_0c6637d282f8ddb7
SQL text: SELECT /* baseline */ COUNT(*) FROM test_baseline
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_82f8ddb7b8066074
Enabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 2118400422

--------------------------------------------
| Id  | Operation          | Name          |
--------------------------------------------
|   0 | SELECT STATEMENT   |               |
|   1 |  SORT AGGREGATE    |               |
|   2 |   TABLE ACCESS FULL| TEST_BASELINE |
--------------------------------------------

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_82f8ddb7fefc82f9
Enabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 3816238667

---------------------------------------------------
| Id  | Operation             | Name              |
---------------------------------------------------
|   0 | SELECT STATEMENT      |                   |
|   1 |  SORT AGGREGATE       |                   |
|   2 |   INDEX FAST FULL SCAN| TEST_BASELINE_IDX |
---------------------------------------------------

Wenn ich die Testquery jetzt noch einmal ausführe, verwendete sie nun auch den Index, da der verbesserte Plan über den Prozeduraufruf akzeptiert wurde:

SQL> SELECT /* test */ COUNT(*) FROM test_baseline;

Abgelaufen: 00:00:00.29

Ausführungsplan
----------------------------------------------------------
Plan hash value: 3816238667

-----------------------------------------------------------------------------------
| Id  | Operation             | Name              | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |                   |     1 |   430   (2)| 00:00:07 |
|   1 |  SORT AGGREGATE       |                   |     1 |            |          |
|   2 |   INDEX FAST FULL SCAN| TEST_BASELINE_IDX |   814K|   430   (2)| 00:00:07 |
-----------------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for this statement
   - SQL plan baseline "SYS_SQL_PLAN_8713193efefc82f9" used for this statement


Statistiken
----------------------------------------------------------
         20  recursive calls
         14  db block gets
       1187  consistent gets
        161  physical reads
       5916  redo size
        342  bytes sent via SQL*Net to client
        338  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

Ich hatte mit diesem Test ein paar Schwierigkeiten, aber nach einem Blick in Tim Halls Erläuterungen wurde der Fall deutlich klarer.

Keine Kommentare:

Kommentar veröffentlichen