Mittwoch, Februar 29, 2012

SSAS-Ressourcen

Ein paar Links zu diversen SSAS-Fragestellungen:
Beim Versuch, die Inhalte zu umschreiben, wird mir klar, dass die Blog-Titel offenbar schon ziemlich genau erklären, worum es geht...

Tom Kyte über SQL Injection

Eines der Lieblingsthemen von Tom Kyte ist die SQL Injection, zu der er vor einigen Wochen einen sehr interessanten Artikel in seinem Blog veröffentlicht hat. Unter anderem zeigt er darin, was für seltsame (und gefährliche) Dinge man mit NLS_DATE_FORMAT anstellen kann.

Verlinkt ist dort auch noch ein Web Seminar des Herrn Kyte, das ich mir gelegentlich auch noch anschauen sollte.

Coalesce statt NVL

Uwe Küchler zeigt in seinem Blog, dass die short-circuit evaluation für coalesce leider nicht für sequences gilt: eine sequence wird also auch dann inkrementiert, wenn bereits vorab ein TRUE-Fall im coalesce erreicht wurde. Darüber hinaus empfiehlt (auch) der Herr Küchler den Verzicht auf NVL, das die short-circuit evaluation überhaupt nicht beherrscht.

Explain Plan für Remote-Zugriffe

Es gibt bekanntlich eine ganze Reihe von Gründen dafür, den Aussagen von Explain Plan mit einem leichten Misstrauen zu begegnen, da sie nicht immer so ganz den Tatsachen der tatsächlichen Ausführung einer Query entsprechen. Trotzdem verwende ich Explain Plan gerne, um eine Vorstellung vom wahrscheinlichen Ausführungsplan zu bekommen, vor allem dann, wenn meine Queries keine Bind-Variablen enthalten. Zur Anzeige der Pläne verwende ich dann normalerweise dbms_xplan.display. Aber offenbar gibt es Fälle, in denen diese nützliche table function nicht tut, was ich von ihr erwarte. Ein solches Beispiel betrifft den Remote-Zugriffe via DB-Link. Dazu ein kleiner Test:

Gegeben ist eine Datenbank der Version 11.1.0.7, in der database links auf mehrere ältere Datenbank definiert sind. Zunächst ein lokaler Zugriff:

-- Explain für einen lokalen Zugriff auf dual:
explain plan for select * from dual;

EXPLAIN PLAN ausgeführt.

select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------
Plan hash value: 272002086

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |     2 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| DUAL |     1 |     2 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------

Jetzt ein Zugriff auf eine zweite Datenbank der Version 10.2.0.4:

-- Explain für einen remote-Zugriff auf dual in einer DB 10.2.0.4:
explain plan for select * from dual@db_10;

EXPLAIN PLAN ausgeführt.

select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------
Plan hash value: 272002086

----------------------------------------------------------------------------------------
| Id  | Operation              | Name | Rows  | Bytes | Cost (%CPU)| Time     | Inst   |
----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT REMOTE|      |     1 |     2 |     2   (0)| 00:00:01 |        |
|   1 |  TABLE ACCESS FULL     | DUAL |     1 |     2 |     2   (0)| 00:00:01 |  DB_10 |
----------------------------------------------------------------------------------------

Note
-----
   - fully remote statement

Nun zum interessanten Fall des Zugriffs auf eine DB 9.2.0.6

-- Explain für einen remote-Zugriff auf dual in einer DB 9.2.0.6:
explain plan for select * from dual@db_9;

EXPLAIN PLAN ausgeführt.

select * from table(dbms_xplan.display());

PLAN_TABLE_OUTPUT
-----------------------------------------------------
Error: cannot fetch last explain plan from PLAN_TABLE

Ursache der Meldung ist aber offenbar nicht das Fehlen von Angaben in der plan_table:

select PLAN_ID, OPERATION, OPTIONS, OBJECT_NAME from plan_table;

PLAN_ID OPERATION                      OPTIONS                        OBJECT_NAME
------- ------------------------------ ------------------------------ -----------
        SELECT STATEMENT               REMOTE
        TABLE ACCESS                   FULL                           DUAL

Verantwortlich für das Verhalten ist die PLAN_ID, die nur für die Ausführung der Query in der 9er Datenbank NULL ist, aber in den neueren DBs einen numerischen Wert enthält. In Ermanglung von SQL-Trace basiert diese Aussage auf folgendem Test:

-- im Fall der DB 9.2.0.6:
update plan_table set PLAN_ID = 4711 where PLAN_ID is null;

2 Zeilen wurden aktualisiert.

SQL> select * from table(dbms_xplan.display());

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

------------------------------------------------
| Id  | Operation              | Name | Inst   |
------------------------------------------------
|   0 | SELECT STATEMENT REMOTE|      |        |
|   1 | TABLE ACCESS FULL      | DUAL |   DB_9 |
------------------------------------------------

Note
-----
   - fully remote statement


Im denke nicht, dass man in der plan_table mit einem solchen Update viel kaputt machen kann, würde aber vermutlich für die Auswertung dann doch eher auf eine traditionelle Auswertungs-Query zurückgehen.


Freitag, Februar 17, 2012

Dynamic Sampling Beobachtungen

Dieser Tage hat Randolf Geist auf der Seite All Things Oracle (wo man eine ziemlich illustre Beiträgerschar versammelt hat) eine Serie zum dynamic sampling gestartet:
  • Dynamic Sampling (I), an Introduction – Part 1: erklärt die Grundlagen des Features, darunter folgende zentrale Punkte:
    • dynamic sampling ruft overhead hervor, da zusätzliche Queries auf rekursiver Ebene abgesetzt werden.
    • das Verhalten hängt von der Existenz von Objektstatistiken ab.
    • es existieren sanity-checks, die beeinflussen, ob die Ergebnisse des dynamic sampling tatsächlich berücksichtigt werden.
    • dynamic sampling erzeugt nur eine relativ schmale Auswahl von Statistik-Informationen (Size in Blocks, Cardinality, Selectivity, NDV)
    • dbms_xplan zeigt die Verwendung von  dynamic sampling  (gleiches gilt auch für autotrace und explain plan)
    • außerdem erwähnt der Herr Geist noch einige der nützlichen Trace-Hilfsmittel aus 11g
  • Dynamic Sampling (I), an Introduction – Part 2: bringt die ausführlichen Test-Scripts, die beim Herrn Geist natürlich nicht fehlen dürfen ("you might wonder if I let you get away without an actual test case, and of course I won’t do so"). Dabei liefert er die Details aus den CBO-Traces (Event 10053)
  • Dynamic Sampling (II) – Controlling the Activity – Part 1: erläutert den Zusammenhang zwischen dem Parameter OPTIMIZER_DYNAMIC_SAMPLING und dem Hint DYNAMIC_SAMPLING: auf Statement-Ebene (oder cursor-Ebene) entspricht die Wirkung des Hints der des Parameters, aber auf Tabellen-Ebene ist die Bedeutung eine andere. Der Statement-Level-Hint führt ab Level 3 das Sampling auch dann durch, wenn table statistics existieren, aber nur für den Fall, dass expressions (also Funktionsaufrufe) für (mindestens) eine Bedingung enthalten sind. Ab Level 4 werden auch mögliche Korrelationseffekte überprüft (wenn also mehrere Bedingungen kombiniert sind).
  • Dynamic Sampling (II) – Controlling the Activity – Part 2: erläutert, dass auch bei der Verwendung von dynamic sampling immer noch auf die Tabellenstatistiken zugegriffen wird, um basic table size und cardinality zu bestimmen - nur die selectivity der Prädikate wird neu bestimmt, so dass unter Umständen immer noch auf fehlerhafte Angaben zurückgegriffen wird. Um das zu vermeiden, kann man den (nicht wirklich dokumentierten) Hint DYNAMIC_SAMPLING_EST_CDN verwenden, der dazu führt, dass auch die table blocks und base cardinality Angaben durch das dynamic sampling ermittelt werden. Darüber hinaus wird die table-Level-Variante des  DYNAMIC_SAMPLING-Hints untersucht. Diese Variante führt in jedem Fall zum Sampling, also unabhängig von den Bedingungen für den Statement-Level-Hint. Allerdings finden auch in diesem Fall sanity checks statt, so dass das Ergebnis des Samplings nicht in jedem Fall verwendet wird. Ein solcher Fall ist z.B. das Fehlen von Prädikaten in der Query. Es folgen noch Hinweise auf das Verhalten im Fall von Parallelisierung (die Parallelisierung des Samplings kann oberhalb der Parallelisierung der Query liegen) und beim Vorliegen Benutzer-definierter Statistiken (oder Profiles: in diesen Fällen wird dynamic sampling nicht verwendet, in der Annahme, dass sie jemand etwas bei der Statistikdefinition gedacht haben dürfte).
  • Dynamic Sampling (III) – Real-Life Data – Part I: beschäftigt sich mit dem Problem der physikalischen Clusterung von Daten (in der Regel basierend auf der Ankunft der Daten in der heap Tabelle), die vom dynamic_sampling nicht sinnvoll berücksichtigt werden kann, da die Anzahl der betrachteten Blocks normalerweise relativ klein ist. Es folgt ein interessantes Fallbeispiel, in dem der Zusammenhang zweier korrelierter Spalten nicht erkannt wird und weder dynamic_sampling, noch extended statistics, noch ein Index für die Spaltenkombination zu brauchbaren cardinality Abschätzungen führt.
  • Dynamic Sampling (III) – Real-Life Data – Part II: setzt die Untersuchung des vorangehenden Artikels fort und liefert eine Lösung für das Problem: nämlich die Verwendung des Hints  DYNAMIC_SAMPLING_EST_CDN, der dazu führt, dass die Index-Statistiken des zuvor für die Spaltenkombination definierten Index berücksichtigt werden. Allerdings gibt es eine ganze Reihe von Rahmenbedingungen, die in diesem Fall für die Berücksichtigung der Index-Statistiken gegeben sein müssen - und die ich hier jetzt nicht detailliert nacherzähle.
--------------------------------------------------------------------------------------------------

Vor Veröffentlichung des dritten Artikels hatte ich mich mit der Frage beschäftigt, in welchen Fällen dynamic sampling bei der Verwendung von Hints wirksam wird, wenn bereits Objekt-Statistiken für eine Tabelle existieren (wobei mein Ergebnis nicht vollständig ist, wie man im angesprochenen Artikel nachlesen kann - unter anderem deshalb, weil meine Test-Queries keine Bedingungen enthalten, weshalb der Statement-Level-Hint ziemlich witzlos ist). Dazu ein ganz einfacher Test:

create table test_dynamic_sampling
as
select rownum id
  from dual
connect by level <= 1000000;

exec dbms_stats.gather_table_stats(user, 'test_dynamic_sampling')

exec dbms_monitor.session_trace_enable()

-- ohne Hint
select count(*) 
  from test_dynamic_sampling;

-- Statement-Level-Hint ohne Angabe eines Sampling Levels
select /*+ dynamic_sampling */ 
       count(*) 
  from test_dynamic_sampling;

-- Statement-Level-Hint mit Angabe eines Sampling Levels
select /*+ dynamic_sampling(10) */ 
       count(*) 
  from test_dynamic_sampling;

-- Table-Level-Hint mit Sampling Level 2
select /*+ dynamic_sampling(test_dynamic_sampling 2) */ 
       count(*) 
  from test_dynamic_sampling;

-- Table-Level-Hint mit Sampling Level 10
select /*+ dynamic_sampling(test_dynamic_sampling 10) */ 
       count(*) 
  from test_dynamic_sampling

exec dbms_monitor.session_trace_disable()

Im Trace-File erscheint nur für die Queries mit einem expliziten Tabellen-bezogenen Hint (also die beiden letzten Fälle) die rekursive Query zum dynamic sampling. Diese Queries haben folgende Struktur (hier behutsam formatiert):

-- für /*+ dynamic_sampling(test_dynamic_sampling 2) */
SELECT /* OPT_DYN_SAMP */ /*+ ALL_ROWS IGNORE_WHERE_CLAUSE NO_PARALLEL(SAMPLESUB) 
       opt_param('parallel_execution_enabled', 'false') NO_PARALLEL_INDEX(SAMPLESUB) NO_SQL_TUNE */
       NVL(SUM(C1),:"SYS_B_0")
     , NVL(SUM(C2),:"SYS_B_1") 
  FROM (SELECT /*+ NO_PARALLEL("TEST_DYNAMIC_SAMPLING") FULL("TEST_DYNAMIC_SAMPLING") NO_PARALLEL_INDEX("TEST_DYNAMIC_SAMPLING") */ 
               :"SYS_B_2" AS C1
             , :"SYS_B_3" AS C2 
          FROM "TEST_DYNAMIC_SAMPLING" SAMPLE BLOCK (:"SYS_B_4" , :"SYS_B_5") SEED (:"SYS_B_6") "TEST_DYNAMIC_SAMPLING") SAMPLESUB;

-- für /*+ dynamic_sampling(test_dynamic_sampling 10) */
SELECT /* OPT_DYN_SAMP */ /*+ ALL_ROWS opt_param('parallel_execution_enabled', 'false') NO_PARALLEL(SAMPLESUB) 
       NO_PARALLEL_INDEX(SAMPLESUB) NO_SQL_TUNE */ 
       NVL(SUM(C1),:"SYS_B_0")
     , NVL(SUM(C2),:"SYS_B_1") 
  FROM (SELECT /*+ NO_PARALLEL("TEST_DYNAMIC_SAMPLING") FULL("TEST_DYNAMIC_SAMPLING") NO_PARALLEL_INDEX("TEST_DYNAMIC_SAMPLING") */ 
               :"SYS_B_2" AS C1
             , :"SYS_B_3" AS C2 
          FROM "TEST_DYNAMIC_SAMPLING" "TEST_DYNAMIC_SAMPLING") SAMPLESUB;


Der - nicht unerwartete - Unterschied der beiden OPT_DYN_SAMP-Queries liegt im Sampling, das im Fall des Levels 10 entfällt, da dabei die komplette Tabelle gelesen wird.

Dass der Hint mit Tabellen-Bezug das dynamic sampling hervorruft, hätte ich wahrscheinlich der Dokumentation entnehmen können, mit Sicherheit aber einem zugehörigen Artikel von Jonathan Lewis.

Nachtrag 30.03.2012: wie schon oben angesprochen hat dieser Test ein paar signifikante Schwächen. Unter anderem erfolgt zwar in den beiden letzten Fällen ein Sampling, aber das Ergebnis wird wieder verworfen, wie man im vierten Artikel der Geist'schen Reihe lesen kann: "The results weren’t used because statistics exist and no predicate is applied for which the selectivity could have been determined using Dynamic Sampling. Since by default only the selectivity is taken from Dynamic Sampling the result of the Dynamic Sampling will be ignored. Now one might argue that performing Dynamic Sampling in such a case is pointless in first place, but that is the way the code currently works." Dass das Sampling nicht greift, zeigt bereits autotrace:

select /*+ dynamic_sampling(test_dynamic_sampling 2) */
       count(*)
  from test_dynamic_sampling;

  COUNT(*)
----------
   1000000

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

------------------------------------------------------------------------------------
| Id  | Operation          | Name                  | Rows  | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                       |     1 |   433   (3)| 00:00:06 |
|   1 |  SORT AGGREGATE    |                       |     1 |            |          |
|   2 |   TABLE ACCESS FULL| TEST_DYNAMIC_SAMPLING |  1000K|   433   (3)| 00:00:06 |
------------------------------------------------------------------------------------

select /*+ dynamic_sampling(test_dynamic_sampling 2) */
       count(*)
  from test_dynamic_sampling
 where id < 1000;

  COUNT(*)
----------
       999

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

--------------------------------------------------------------------------------------------
| Id  | Operation          | Name                  | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                       |     1 |     4 |   436   (3)| 00:00:06 |
|   1 |  SORT AGGREGATE    |                       |     1 |     4 |            |          |
|*  2 |   TABLE ACCESS FULL| TEST_DYNAMIC_SAMPLING |    19 |    76 |   436   (3)| 00:00:06 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("ID"<1000)

Note
-----
   - dynamic sampling used for this statement

Nur im zweiten Fall mit einer Bedingung erscheint der Hinweis "dynamic sampling used for this statement" - wobei die Rows-Angabe in diesem Fall ziemlich daneben liegt, was an der Größe des samples liegt und in meinem Test erst ab Level 7 zu einem akkuraten Ergebnis führt. Man könnte hier noch allerlei Beispiele anfügen, aber die findet man auch alle schon in Randolf Geists Artikelserie.

Mittwoch, Februar 15, 2012

Fortschrittsangaben in v$session_longops

Heute ist mir zum ersten Mal aufgefallen, dass in v$session_longops nicht unbedingt immer plausible Angaben erscheinen:

   SOFAR  TOTALWORK MESSAGE                                             SQL_PLAN_OPERATION  SQL_PLAN_OPTIONS 
-------- ---------- --------------------------------------------------- ------------------- -----------------
 1138348     861414 Sort Output:  : 1138348 out of 861414 Blocks done   WINDOW              SORT             

Entsprechend zeigt auch DBMS_SQLTUNE.REPORT_SQL_MONITOR einen Progress von deutlich über 100%. Es handelt sich dabei um eine WINDOW SORT Operation einer analytischen Funktion, also nicht um einen Wert der mit dbms_application_info.set_session_longops eingefügt wurde, sondern um interne Operationsangaben.

Davon abgesehen deutet sich mal wieder an, dass man mit Analytics auf großen Datenmengen vorsichtig sein sollte, da die WINDOW SORT Operation nicht ganz unproblematisch ist - wozu Jonathan Lewis Grundsätzliches gesagt hat; zwei Randbemerkungen zum Thema habe ich hier und hier gegeben. In solchen Fällen gehe ich unter Umständen auf die Verwendung eines Joins der Basistabelle mit einem aggregierten Zwischenergebnis zurück.

Sonntag, Februar 12, 2012

DBMS_SCHEDULER-Visualisierung mit SQL Developer 3.1

Tim Hall, der vor ein paar Jahren ein ganzes Buch zu DBMS_JOBS und DBMS_SCHEDULER geschrieben hat, zeigt in seinem Blog, was der aktuelle SQL Developer 3.1 zu Scheduler-Jobs und Job-Ketten anzubieten hat und liefert dabei auch noch eine Reihe von Links auf weitere Artikel zum Thema Scheduler. Sein Fazit lautet: "I would currently define all job chains using the PL/SQL API, so I could save my creation script in source control, and just use the SQL Developer interface to view the job chains I've created. With a bit of luck, this situation will change in subsequent versions."

Donnerstag, Februar 09, 2012

I/O-Tests

Kevin Closson überfordert mich häufig bereits mit den Titeln seiner Blog-Artikel - und nicht selten auch mit den Inhalten - aber diesmal verstehe ich, dass er sein bewährtes SLOB-Tool (The Silly Little Oracle Benchmark) vorstellt, das offenbar deutlich mehr kann als das Orion-Tool (das dort auch verlinkt ist).

Nachtrag 16.05.2012: Yuri Velikanov hat im Pythian Blog zwei Artikel zu seinen Tests mit SLOB veröffentlicht und dort findet man dazu dann noch mal einige Kommentare des Herrn Closson.

Dienstag, Februar 07, 2012

Plan-Interpretation

Christian Antogninis Erläuterungen zur Interpretation von Execution Plans, die man in Kapitel 6 seines großartigen Buches Troubleshooting Oracle Performance und - in knapper Form - auch im Web findet, gehören für mich zu den wichtigsten Grundlagen für die Deutung von SQL-Zugriffen. Im Blog-Eintrag Index Scan with Filter Predicate Based on a Subquery zeigt der Autor jetzt einen jener Fälle, in denen die allgemeinen Interpretationsregeln nicht gelten. Sein Fazit lautet: "In summary, be careful when you see an index scan with a filter predicate applying a subquery. The execution plan might not be carried out as you expect at first sight. It is also essential to point out that in such a case the predicate information is essential to fully understand what’s going on." Auf die Prädikat-Informationen würde ich aber bei der Plandeutung ohnehin selten verzichten wollen, da sie für die Überprüfung von Cardinality-Schätzungen entscheidend sind.

Nachtrag 06.03.2012: Jonathan Lewis hat zum Thema auch noch einen Artikel geschrieben, der auf Christian Antogninis Blog-Eintrag Bezug nimmt.

Interval Partitioninig und das Ende aller Tage

Dass das Interval Partitioning leider noch ein paar unerfreuliche Bugs enthält, habe ich gelegentlich schon mal erwähnt. Heute ist mir aufgefallen, dass die Verwendung des sehr beliebten Datums-Default-Werts '31.12.9999' bei einer nach einer Datumsangabe intervall partitionierten Tabelle nicht ganz das gewünschte Ergebnis bringt:

create table test_interval_max_default ( mydate date)
partition by range (mydate)
interval (NUMTOYMINTERVAL(1,'MONTH'))
(partition test_p1 values less than (to_date('20120201', 'yyyymmdd')));

Tabelle wurde erstellt.

insert into test_interval_max_default (mydate) values (to_date('30.11.9999', 'dd.mm.yyyy'));

1 Zeile wurde erstellt.

insert into test_interval_max_default (mydate) values (to_date('01.12.9999', 'dd.mm.yyyy'));

insert into test_interval_max_default (mydate) values (to_date('01.12.9999', 'dd.mm.yyyy'))
            *
FEHLER in Zeile 1:
ORA-01841: (Volles) Jahr muss zwischen -4713 und +9999 liegen und darf nicht 0 sein

Nun ist '31.12.9999' sicher ein ziemlich unglücklicher Default-Wert - nicht zuletzt, weil er den CBO, der in Abwesenheit von Histogrammen eine Gleichverteilung der Werte zwischen Minimum und Maximum annimmt, zur massiven Fehleinschätzung von Kardinalitäten führen kann -, aber dass man ihn einfach nicht einfügen kann, erscheint doch etwas drastisch. In diesem Fall handelt es sich aber nicht um einen Bug, sondern um ein dokumentiertes Verhalten:
Note, however, that using a date where the high or low bound of the partition would be out of the range set for storage causes an error. For example, TO_DATE('9999-12-01', 'YYYY-MM-DD') causes the high bound to be 10000-01-01, which would not be storable if 10000 is out of the legal range.
Es bleibt also nur die Verwendung eines anderen Default-Werts für "das Ende aller Tage" oder der Verzicht auf Interval Partitioning.

Nachtrag 09.02.2012: Der Fall ist tatsächlich sogar noch unerfreulicher: bereits eine Query mit einer entsprechenden Bedingung ruft ora-01841 hervor:
SQL> select * from test_interval_max_default where mydate = to_date('31.12.9999', 'dd.mm.yyyy');
select * from test_interval_max_default where mydate = to_date('31.12.9999', 'dd.mm.yyyy')
              *
FEHLER in Zeile 1:
ORA-01841: (Volles) Jahr muss zwischen -4713 und +9999 liegen und darf nicht 0 sein
Angesichts dieses Verhaltens schwindet meine Zuneigung zum Interval Partitioning allmählich - obwohl es eigentlich eine so schöne Idee ist. Nun ja, vielleicht funktioniert es mit Version 12 oder 14.

Sonntag, Februar 05, 2012

Fehlerhaftes Costing für Index Hash Joins

Jonathan Lewis zeigt in seinem Blog, dass die Kostenberechnung für Index Hash Joins in allen aktuellen Oracle-Releases massive Fehler enthält, was zur Folge haben kann, dass diese - in manchen Fällen sehr effiziente - Zugriffsoption nicht in Betracht gezogen wird: "This means the optimizer may be missing opportunities where the index hash join is a good execution path. Keep an eye open for this, you may want to hint some of your SQL (and then switch the hints into SQL Baselines, if you’re running 11g)." Ein Aspekt dabei ist, dass Indizes anhand ihrer Benamung ausgewählt werden, ein anderer, dass die Kosten der Einzelschritte nicht sinnvoll summiert werden; außerdem werden für den Index Join zusätzlich zu den erwarteten Kosten eines Index Fast Full Scan  noch die Kosten eines Index Full Scan aufgeschlagen, was inhaltlich nicht einleuchtet (und offenbar erst im 10053er Trace sichtbar wird).

Ich wollte mir noch eine passende Begriffsbestimmung für den Index Hash Join ausdenken, aber die findet man natürlich auch schon beim Herrn Lewis:
One of the less well known access paths available to the optimizer is the “index join” also known as the “index hash join” path. It’s an access path that can be used when the optimizer decides that it doesn’t need to visit a table to supply the select list because there are indexes on the table that, between them, hold all the required columns.
Und der Vollständigkeit halber dann auch noch ein dritter Link zum Scratchpad, diesmal auf ein Fallbeispiel, in dem eine Tabelle mehrfach referenziert wird, um den cbo auf die Idee zu bringen, für jeden Fall den geeigneten Index zu verwenden.

Hier angekommen erinnere ich mich daran, dass ich schon mal auf einen der Index-Join-Artikel verwiesen hatte - und da das dann bereits Teil 4 einer Serie war, ist es vermutlich einfacher gleich auf die entsprechende Kategorie im Scratchpad zu verweisen, die aktuell fünf Artikel enthält.

Freitag, Februar 03, 2012

SQL-Monitor-Darstellungsprobleme im RAC

Nur eine kurze Notiz, zu der ich vielleicht gelegentlich die Pointe nachliefern kann: in RAC-Systemen liefert dbms_sqltune.report_sql_monitor - natürlich - nur dann die gewünschten Ergebnisse, wenn man einen sinnvollen Wert für den Parameter INST_ID setzt:
INST_ID: Only considers statements started on the specified instance. Use -1 (the default) to target the login instance. NULL will target all instances.
Die NULL-Variante fällt mir erst jetzt auf (und wird dann in der nächsten Woche getestet), aber wenn man den Aufruf für einen anderen Knoten als den, auf dem man sich gerade befindet, ausführt, dann liefert die table function (zumindest in dem System mit dem ich dieser Tage arbeite) einen relativ unformatierten Plan, in dem die Einrückungen fehlen. Um das zu vermeiden, verbinde ich mich in solchen Fällen direkt mit dem entsprechenden Knoten, aber die Ursache des Verhaltens ist mir unklar - und ich kann ohne größeren Aufwand kein 10046er Trace auf den Maschinen durchführen, um die Unterschiede der beiden Varianten zu bestimmen. Falls NULL hier Abhilfe schafft oder falls mir eine Erklärung über den Weg läuft, trage ich das noch nach.

Nachtrag 26.07.2012: Ohne der Frage weiter nachgegangen zu sein, nehme ich inzwischen an, dass die Table-Function zumindest in einigen Fällen nur auf die V$-Views und nicht auf die GV$-Versionen zugreift.