Posts mit dem Label Data Dictionary werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Data Dictionary werden angezeigt. Alle Posts anzeigen

Freitag, April 21, 2017

postgres Statistiken: pg_stat_all_tables

Alexey Lesovsky hat bereits eine ganze Reihe interessanter Artikel im Rahmen einer Serie "Deep dive into postgres stats" veröffentlicht. Bisher hatte ich mich davor gedrückt, diese Ausführungen zusammenzufassen, aber heute ist ein ruhiger Tag und die pg_stat_all_tables scheint mir eine besonders interessante Quelle zu sein...

Die View pg_stat_all_tables enthält eine ganze Reihe interessanter Informationen zur Nutzung von Tabellen. Unter anderem weist sie hin auf:
  • Zugriffsinformationen: die View enthalt Informationen zur Anzahl sequentieller Scans (also Full Table Scans, wie man sie in anderen RDBMS nennt) und zur Anzahl von Index-Zugriffen sowie zur Anzahl der dabei zurückgelieferten Datensätze. Eine hohe Zahl sequentieller Scans auf großen Tabellen deutet häufig auf fehlende Indizes hin. Weiterhin könnten Index-Zugriffe mit hohen durchschnittlichen Satzanzahlen auf wenig selektive Index-Zugriffe hindeuten.
  • Schreiboperationen: zeigt die Menge der DML-Operationen auf den Tabellen. Dabei wird zwischen Standard- und HOT-(=Heap-only tuples) Updates unterschieden, wobei letztere wünschenswert sind, da sie Indizes nicht aktualisieren, wenn ein Update keine inidzierten Werte verändert. Allerdings funktionieren HOT-Updates nur, wenn die zugehörige page ausreichend Platz übrig hat. Für Tabellen, bei denen sich HOT-Updates kaum ergeben, könnte eine Anpassung des Fillfactors sinnvoll sein (der nur für neue Datensätze berücksichtigt wird, den Platzverbrauch des Objekts vergrößert und in diesem Zusammenhang nur dann relevant ist, wenn es tatsächlich updates ohne Bezug auf indizierte Spalten gibt).
  • die autovacuum queue: seit 9.6 kann man bestimmte Informationen zum autovacuum aus der View pg_stat_progress_vacuum bekommen. Was fehlt ist allerdings weiterhin eine Liste der Tabellen, für die aktuell ein vacuum-Lauf erforderlich wäre. Dazu hat der Autor eine komplexe Query (die unter anderem auf pg_stat_all_tables zugreift) bereitgestellt, mit deren Hilfe sich die Länge der autovacuum queue bestimmen lässt. Basierend auf den Aussagen der Query kann man dann über Maßnahmen zur Optimierung des autovacuum nachdenken (Erhöhung der autovacuum_max_workers oder Anpassung anderer autovacuum-Parameter).
Im Vergleich zu dem, was andere RDBMS an internen Statistiken liefern, sind die Informationen bei postgres überschaubar, erlauben aber durchaus interessante Analysen.

Dienstag, April 18, 2017

postgres Extensions

Zwei interessante Hinweise findet man im neusten postgres-Artikel von Daniel Westermann:
  • das data dicitionary von postgres liefert zahlreiche Informationen zu den verfügbaren und den installierten Extensions:
    •  pg_available_extensions: zeigt die verfügbaren und die installierten Extensions inklusive eines Kommentars zu ihrer Funktion.
    • pg_available_extension_versions: liefert weitere Detailinformationen zu den Extensions, unter anderem zu den Abhängigkeiten, die zwischen den Erweiterungen bestehen.
    • pg_extension: liefert Informationen zu den installierten Extensions. Dabei weichen die Informationen von denen ab, die der psql-Shortcut \px liefert.
  • beinahe ebenso interessant ist der zweite Hinweis, den der Herr Westermann liefert: mit dem Switch: "\set ECHO_HIDDEN on" kann man die den psql-Shortcuts zugrunde liegenden Befehle anzeigen lassen. Mir war zwar klar, dass das irgendwie möglich sein sollte, aber bisher hatte ich mir nie die Mühe gemacht, nach dieser Option zu suchen.

Montag, Dezember 12, 2016

Bestimmung der Verwendung von Indizes in 12.2

Bis Oracle 12.2 zum Download verfügbar ist, werde ich mich damit wohl nur theoretisch beschäftigen - also durch die Lektüre von Artikeln zum Thema. Eine der besten Quellen solcher Artikel ist derzeit der Blog von dbi Services - insbesondere, wenn der zugehörige Autor Franck Pachot heißt. Einer dieser Artikel behandelt das in 12.2 eingeführte erweiterte Monitoring zur Index-Verwendung und liefert unter anderem folgende Informationen:
  • die View dba_index_usage liefert kumulierte Nutzungsstatistiken für jeden Index.
  • in v$index_usage_info finden sich Nutzungsstatistiken seit dem letzten Flush (der alle 15 min stattfindet).
  • üblicherweise basieren die Angaben auf Sampling, können bei Bedarf auch für jede Ausführung erfasst werden (was natürlich einen Performance-Overhead hervorruft).
  • auch die Verwendung eines Index im Rahmen der gather_%_stats-Aufrufe werden erfasst, so dass die Angaben nur begrenzt Auskunft darüber geben, ob ein Index für Anwendungs-Zugriffe relevant ist.
  • die Verwendung eines Index auf einem Foreign Key zur Vermeidung eines table locks (Stichwort: deadlock-Ursache Nr. 1) wird hingegen in den Statistiken nicht erfasst.
Zusammenfassend lässt sich feststellen, dass dieses Monitoring ein Schritt in die richtige Richtung ist, aber nicht ausreicht, um die klassische Frage "kann ich diesen Index gefahrlos löschen?" zu beantworten. Zumal das Thema der Verwendung von Index-Statistiken für den Optimizer ohnehin nicht betroffen ist - aber das kann man dem Monitoring aus meiner Sicht nicht ernsthaft vorwerfen.

Montag, September 05, 2016

Dictionary Metadaten in der Multitenant Infrastruktur

Franck Pachot hat seit 2014 eine Reihe interessanter Artikel zu den internen Implementierungsdetails der Dictionary-Zugriffe im Multitenant-Kontext veröffentlicht. Ich spare mir eine detaillierte Zusammenfassung der umfangreichen Inhalte und beschränke mich auf die Verweise:
Die Wahrscheinlichkeit ist hoch, dass ich damit nicht alle Artikel zum Thema erfasst habe, aber mir ging es eher darum, das Thema überhaupt einmal zu notieren, da ich bisher in Sachen Multitenant noch solide Wissenslücken habe.

    Montag, Juli 11, 2016

    Staleness IMPORT für Materialized Views in all_mviews nach data pump Import

    Eine kurze Notiz, da ich bei meiner Recherche im Netz nicht arg viel zum Thema gefunden habe: durch den Transport einer Materialized View über data pump (expdp - impdp) werden zwar die Daten des Segments transportiert, aber die MView wird nicht mehr für Query Rewrite verwendet und auch ein Fast Refresh ist nicht mehr möglich und scheitert an einem Fehler ORA-12034:
    ORA-12034: materialized view log on "XXX"."YYY" younger than last refresh
    An dieser Stelle ist daher ein complete refresh erforderlich. Insofern ist es vermutlich sinnvoller, auf den Transfer der MView-Daten komplett zu verzichten und sie lieber gleich im Zielsystem neu aufzubauen. Besonders überraschend ist das nicht - auch wenn sich dafür vermutlich eine Lösung bei der Implementierung hätte finden lassen, wenn man sich darum bemüht hätte -, aber falls ich es schon mal wusste, habe ich es vergessen. Überraschender ist vielleicht der Status IMPORT, der in diesem Fall für die MView in der Spalte STALENESS in DBA_MVIEWS auftaucht - obwohl die Dokumentation diesen Status nicht erwähnt; dort findet man nur den Hinweis auf die ergänzende Spalte UNKNOWN_IMPORT: "Indicates whether the materialized view is imported (Y) or not (N)". Gänzlich undokumentiert ist dieser Staleness-Status aber nicht: man findet ihn im MOS-Dokument "How to Perform an EXPDP/IMPDP for a SCHEMA with a MATERIALIZED VIEW. Highlighting the IMPORT Staleness and UNKNOWN_IMPORT column in DBA_MVIEWS (Doc ID 1579866.1)" und dort wird das Verhalten auch noch mal anhand eines Beispiels dargestellt.

    Montag, Dezember 28, 2015

    Langsamer Zugriff auf v$sql

    Ein ganz wohlfeiler Trick, um in diesem Jahr noch auf 94 Blog-Einträge zu kommen - aber keine Angst: 100 werden es diesmal nicht mehr. Kurz vor Weihnachten habe ich im OTN-Forum General Database einen Fall vorgestellt, in dem der Zugriff auf eine eigentlich sehr übersichtliche dynamische Performance-View v$sql (mit kaum mehr als 2000 Einträgen) stabil 25 Sekunden dauert. Dabei sind anscheinend keine Waits im Spiel, sondern es wird fast ausschließlich CPU verbrannt. Trotz zahlreicher sachdienlicher Hinweise von Jonathan Lewis, Stefan Koehler und anderen Beiträgern ist bisher noch keine endgültige Erklärung in Sicht, aber großartig war es, mal wieder zu sehen, wie viele nützliche Antworten man auf OTN bekommt, wenn man eine höfliche Frage stellt. Und wenn ich schon dabei bin, mich bei den OTN-Kommentatoren zu bedanken, dann kann ich gleich auch noch einen Dank an meine treuen Leser und gute Wünsche fürs Folgejahr ergänzen.

    Montag, Dezember 07, 2015

    Views für die Postgres Performance Analyse

    Nur ein Link auf eine sehr nützliche Grafik, in der Alexey Lesovsky zeigt, welche interne View jeweils verwendet werden kann um bestimmte Performance-Fragestellungen zu beantworten. Sollte ich mir irgendwo an die Wand hängen.

    Montag, August 03, 2015

    Library Cache Verschmutzung

    Da ich den Fall hier eher wiederfinde als im OTN-Forum erzähle ich an dieser Stelle kurz einen Thread nach, den ich dort vor einigen Tagen begonnen habe. Meine Frage im Forum lautete: wieso sehe ich in einer RAC-Instanz (11.2.0.3, zwei Knoten, Standard Edition) diverse Queries, zu denen zahlreiche child cursor existieren, obwohl die Queries keine Bindewerte enthalten, so dass die Pläne eigentlich problemlos wiederverwendbar sein sollten? Die Antwort auf die Frage haben mir John Spencer, Randolf Geist und Mohamed Houri geliefert, die die fehlenden Einträge in meinem Lückentext ergänzten. Insbesondere wies mich Randolf darauf hin, dass in der Standard Edition eigentlich keine Parallelisierung im Spiel ist, aber Zugriffe auf gv$-Objekte intern trotzdem parallel operieren - was dazu passte, dass die fraglichen Queries alle auf dynamische Performance-Views zugriffen. Außerdem merkte er an, dass die Spalte REASON in gv$sql_shared_cursor nicht zum Spass eingeführt wurde, sondern wichtige Begründungsdetails dafür liefert, warum ein Plan nicht wiederverwendet werden kann. Möglicherweise hätte ich sofort genauer auf diese Angaben geschaut, wenn ich Tanel Poders nonshared Skript verwendet hätte, auf das Mohamed verwies, und das ein Pivoting der View-Informationen durchführt und die Reason exponiert darstellt. Der Angabe war dann - mehr oder minder - deutlich zu entnehmen, dass hier eine Abweichung zwischen dem parallel_query_default_dop und dem kxfr_Default_DOP vorliegt, was genau zum von John erwähnten "Bug 14711917 - High version count in RAC due to PX_MISMATCH (Doc ID 14711917.8)" passt. Die Ursache für die Abweichung liegt im gegebenen Fall wohl darin, dass die beiden RAC-Knoten tatsächlich einen unterschiedlichen CPU_COUNT besitzen. Den könnte man theoretisch korrigieren, aber sinnvoller erscheint die Verwendung des zugehörigen Patches oder das Upgrade auf 11.2.0.4. Einmal mehr zeigt sich, dass die OTN-Foren eine sehr große Hilfe bei der Problemlösung sein können. Im Thread habe ich meine Beobachtungen recht umfassend dokumentiert - und das spare ich mir deshalb hier.

    P.S.: der aufmerksame Leser mag einen Zusammenhang mit meiner letzten Notiz hier im Blog vermuten - und da läge er nicht daneben.

    Donnerstag, Juli 30, 2015

    Bizarre Plandarstellung mit dbms_xplan.display_cursor

    Vor einigen Tagen ist mir aufgefallen, dass in einer Datenbank (11.2.0.3), an deren Wartung ich seit kurzem beteiligt bin, für eine harmlose Nagios-Check-Query mit dbms_xplan.display_cursor höchst merkwürdige Pläne generiert wurden, die die einzelnen Schritte des Ausführungsplans in schier endloser Folge wiederholten. Ein Blick in v$sql_plan zeigte, dass hier tatsächlich extrem viele Plan-Duplikate vorlagen, aus v$sql_shared_cursor war zu ersehen, dass die Begründung der neuen Cursor in der Regel mit OPTIMIZER_MISMATCH oder PX_MISMATCH angegeben war, und aus v$sql ging hervor, dass die meisten dieser Cursor mit dem Flag IS_OBSOLETE=Y gekennzeichnet waren, das die Dokumentation folgendermaßen erläutert: "Indicates whether the cursor has become obsolete (Y) or not (N). This can happen if the number of child cursors is too large."

    Ein Blick auf den Ausführungsplan der Query zeigte, dass hier tatsächlich Parallelisierung beim Zugriff auf Dictionary-Objekte im Spiel war, aber das erklärt aus meiner Sicht noch nicht die Häufigkeit der Wiederholungen. Aber immerhin war schnell festzustellen, dass Timur Akhmadeev das Problem der bizarren Plandarstellung bereits 2012 beschrieben hat:
    • V$SQL.IS_OBSOLETE: erwähnt die fehlerhafte Plandarstellung und weist darauf hin, dass in solchen Fällen immer höchstens ein "aktiver" Repräsentant pro child_number vorliegt (mit IS_OBSOLETE = 'N') und zusätzlich viele inaktive (IS_OBSOLETE = 'Y').
    • Obsolete cursors: reproduziert das Verhalten mit einem einfachen Testfall: "After the point of 100 child cursors per parent, Oracle builds a new parent (V$SQL.ADDRESS holds its address) cursor, marking old parent and child cursors as obsolete. V$SQLAREA handles the situation well, but V$SQL doesn’t and that’s clearly a bug. The threshold point of when to build a new parent cursor is 100 by default and is controlled with a new hidden parameter _cursor_obsolete_threshold."
    Damit habe ich die Erklärung für das Anzeigeproblem. Jetzt fehlt mir nur noch eine Erklärung für die Erzeugung so vieler Cursor durch einen regelmäßig durchgeführten Nagios-Check mit einer relativ einfachen statischen SQL-Query.

    P.S.: mein nächster Schritt wird die Überarbeitung der Check-Query, die mir ganz grundsätzlich noch nicht so recht gefällt.

    P.P.S.: Stefan Koehler hat mich daran erinnert, dass Christian Antognini auch einen Artikel zum Thema (und insbesondere zum Parameter _cursor_obsolete_threshold) geschrieben hat, was mir vermutlich auch schon mal bekannt gewesen ist - zumindest habe ich den Artikel hier verlinkt.

    Donnerstag, Juli 16, 2015

    Performance-Probleme beim Zugriff auf DBA_FREE_SPACE

    Das Phänomen ist offenbar relativ bekannt, war mir aber bisher nicht begegnet (oder in Erinnerung geblieben): ein Icinga-Test zur Bestimmung des Füllgrads eines Tablespaces erreichte zuletzt Laufzeiten von über einer Minute, was zu Timeouts und Icinga-Fehlern führte. Schnell zu bestimmen war, dass das eigentliche Problem im Zugriff auf DBA_FREE_SPACE lag. Aber warum reagierte diese Dictionary-View so träge? Zur Prüfung habe ich zunächst einen Blick in die Definition in DBA_VIEWS geworfen, aber den hätte ich mir sparen können, denn ein Plan mit rowsource statistics zeigte die gleichen Informationen - und lieferte darüber hinaus die entscheidenden Details. Hier ein entsprechendes Beispiel, das ich mit 12.1.0.2 erstellt habe, und das sich in diesem Release noch einmal interessanter verhält als in 11.2, wo mir das Phänomen ursprünglich begegnet war:

    select /*+ gather_plan_statistics */
           tablespace_name
         , count(*)
      from dba_free_space
     group by tablespace_name
    
    TABLESPACE_NAME                  COUNT(*)
    ------------------------------ ----------
    SYSAUX                                  4
    USERS                                 915
    SYSTEM                                  1
    
    Abgelaufen: 00:00:16.49
    
    Plan hash value: 2867613348
    
    -----------------------------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                    | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
    -----------------------------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT             |                  |      1 |        |      3 |00:00:17.80 |     828K|    373K|       |       |          |
    |   1 |  HASH GROUP BY               |                  |      1 |      2 |      3 |00:00:17.80 |     828K|    373K|  1452K|  1452K|  760K (0)|
    |   2 |   VIEW                       | DBA_FREE_SPACE   |      1 |     68 |    920 |00:00:00.03 |     828K|    373K|       |       |          |
    |   3 |    UNION-ALL                 |                  |      1 |        |    920 |00:00:00.03 |     828K|    373K|       |       |          |
    |   4 |     NESTED LOOPS             |                  |      1 |      1 |      0 |00:00:00.01 |       6 |      2 |       |       |          |
    |   5 |      NESTED LOOPS            |                  |      1 |      1 |      0 |00:00:00.01 |       6 |      2 |       |       |          |
    |   6 |       INDEX FULL SCAN        | I_FILE2          |      1 |      3 |      3 |00:00:00.01 |       1 |      1 |       |       |          |
    |*  7 |       TABLE ACCESS CLUSTER   | FET$             |      3 |      1 |      0 |00:00:00.01 |       5 |      1 |       |       |          |
    |*  8 |        INDEX UNIQUE SCAN     | I_TS#            |      3 |      1 |      3 |00:00:00.01 |       2 |      0 |       |       |          |
    |*  9 |      TABLE ACCESS CLUSTER    | TS$              |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
    |* 10 |       INDEX UNIQUE SCAN      | I_TS#            |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
    |  11 |     NESTED LOOPS             |                  |      1 |      3 |     16 |00:00:00.01 |      19 |      6 |       |       |          |
    |  12 |      NESTED LOOPS            |                  |      1 |      3 |     16 |00:00:00.01 |      15 |      6 |       |       |          |
    |* 13 |       TABLE ACCESS FULL      | TS$              |      1 |      2 |      3 |00:00:00.01 |       6 |      2 |       |       |          |
    |* 14 |       FIXED TABLE FIXED INDEX| X$KTFBFE (ind:1) |      3 |      2 |     16 |00:00:00.01 |       9 |      4 |       |       |          |
    |* 15 |      INDEX UNIQUE SCAN       | I_FILE2          |     16 |      1 |     16 |00:00:00.01 |       4 |      0 |       |       |          |
    |  16 |     NESTED LOOPS             |                  |      1 |     63 |    904 |00:00:00.17 |     828K|    373K|       |       |          |
    |  17 |      NESTED LOOPS            |                  |      1 |    188 |    904 |00:00:00.17 |     828K|    373K|       |       |          |
    |* 18 |       HASH JOIN              |                  |      1 |     20 |    102 |00:00:00.01 |      10 |      1 |  1483K|  1483K|  695K (0)|
    |* 19 |        TABLE ACCESS FULL     | TS$              |      1 |      2 |      3 |00:00:00.01 |       6 |      0 |       |       |          |
    |* 20 |        TABLE ACCESS FULL     | RECYCLEBIN$      |      1 |     24 |    102 |00:00:00.01 |       4 |      1 |       |       |          |
    |* 21 |       FIXED TABLE FULL       | X$KTFBUE         |    102 |      9 |    904 |00:00:17.54 |     828K|    373K|       |       |          |
    |* 22 |      INDEX UNIQUE SCAN       | I_FILE2          |    904 |      1 |    904 |00:00:00.01 |       4 |      1 |       |       |          |
    |  23 |     NESTED LOOPS             |                  |      1 |      1 |      0 |00:00:00.01 |     302 |      2 |       |       |          |
    |  24 |      NESTED LOOPS            |                  |      1 |      1 |      0 |00:00:00.01 |     302 |      2 |       |       |          |
    |  25 |       MERGE JOIN CARTESIAN   |                  |      1 |     73 |    306 |00:00:00.01 |       5 |      0 |       |       |          |
    |  26 |        INDEX FULL SCAN       | I_FILE2          |      1 |      3 |      3 |00:00:00.01 |       1 |      0 |       |       |          |
    |  27 |        BUFFER SORT           |                  |      3 |     24 |    306 |00:00:00.01 |       4 |      0 |  6144 |  6144 | 6144  (0)|
    |* 28 |         TABLE ACCESS FULL    | RECYCLEBIN$      |      1 |     24 |    102 |00:00:00.01 |       4 |      0 |       |       |          |
    |  29 |       TABLE ACCESS CLUSTER   | UET$             |    306 |      1 |      0 |00:00:00.01 |     297 |      2 |       |       |          |
    |* 30 |        INDEX UNIQUE SCAN     | I_FILE#_BLOCK#   |    306 |      1 |     91 |00:00:00.01 |     206 |      2 |       |       |          |
    |* 31 |      TABLE ACCESS CLUSTER    | TS$              |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
    |* 32 |       INDEX UNIQUE SCAN      | I_TS#            |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
    -----------------------------------------------------------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
    PLAN_TABLE_OUTPUT
    -----------------------------------------------------------------------------------------------------------------------------------------------
    
       7 - filter("F"."FILE#"="FI"."RELFILE#")
       8 - access("F"."TS#"="FI"."TS#")
       9 - filter("TS"."BITMAPPED"=0)
      10 - access("TS"."TS#"="F"."TS#")
      13 - filter(("TS"."CONTENTS$"=0 AND "TS"."BITMAPPED"<>0 AND INTERNAL_FUNCTION("TS"."ONLINE$")))
      14 - filter(("TS"."TS#"="KTFBFETSN" AND INTERNAL_FUNCTION("CON_ID")))
      15 - access("KTFBFETSN"="FI"."TS#" AND "KTFBFEFNO"="FI"."RELFILE#")
      18 - access("TS"."TS#"="RB"."TS#")
      19 - filter(("TS"."CONTENTS$"=0 AND "TS"."BITMAPPED"<>0 AND INTERNAL_FUNCTION("TS"."ONLINE$")))
      20 - filter(("RB"."TS#" IS NOT NULL AND "RB"."FILE#" IS NOT NULL AND "RB"."BLOCK#" IS NOT NULL))
      21 - filter((INTERNAL_FUNCTION("CON_ID") AND "KTFBUESEGBNO"="RB"."BLOCK#" AND "KTFBUESEGFNO"="RB"."FILE#" AND
                  "KTFBUESEGTSN"="RB"."TS#"))
      22 - access("RB"."TS#"="FI"."TS#" AND "KTFBUEFNO"="FI"."RELFILE#")
      28 - filter(("RB"."TS#" IS NOT NULL AND "RB"."FILE#" IS NOT NULL AND "RB"."BLOCK#" IS NOT NULL))
      30 - access("U"."TS#"="RB"."TS#" AND "U"."SEGFILE#"="RB"."FILE#" AND "U"."SEGBLOCK#"="RB"."BLOCK#")
           filter(("U"."TS#"="FI"."TS#" AND "U"."SEGFILE#"="FI"."RELFILE#"))
      31 - filter("TS"."BITMAPPED"=0)
      32 - access("TS"."TS#"="U"."TS#")
    
    Note
    -----
       - this is an adaptive plan
    

    Auffällig sind mehrere Punkte:
    • 16 Sekunden sind ziemlich viel für die Ermittlung dieses Ergebnisses.
    • die Laufzeit entfällt fast komplett auf eine der vier über UNION ALL verknüpften Teilabfragen.
    • in der langsamen Teilabfrage erfolgt ein Zugriff auf RECYCLEBIN$.
    • es handelt sich um einen adaptiven Plan.
    Im 11.2er System habe ich durch einen Blick in den Recyclebin festgestellt, dass dort tatsächlich mehr als 1500 Varianten einer Hilfstabelle lagen, die regelmäßig gelöscht wurde, ohne dass ein Purge des Objekts erfolgte. Nach Durchführung einer Purge-Operation für die Objekt-Inkarnationen (die leider nicht komplett automatisiert erfolgen kann, weil ein PURGE TABLE [object_name] nur die älteste Inkarnation löscht) läuft die Query wieder in weniger als einer Sekunde und das Problem ist gelöst (zumal die Test-Logik angepasst wurde, die für das Löschen der Tabelle verantwortlich war).

    In 12.1.0.2 ist die Lage allerdings eine andere: hier hat der Optimizer nach wenigen Ausführungen genügend Material gesammelt, um die geeignetere Version des adaptiven Plans zu verwenden:

    Plan hash value: 2991530507
    
    -----------------------------------------------------------------------------------------------------------------------------------------------
    | Id  | Operation                    | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |
    -----------------------------------------------------------------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT             |                  |      1 |        |      3 |00:00:00.19 |    8462 |   3669 |       |       |          |
    |   1 |  HASH GROUP BY               |                  |      1 |      2 |      3 |00:00:00.19 |    8462 |   3669 |  1452K|  1452K|  740K (0)|
    |   2 |   VIEW                       | DBA_FREE_SPACE   |      1 |    922 |    920 |00:00:00.04 |    8462 |   3669 |       |       |          |
    |   3 |    UNION-ALL                 |                  |      1 |        |    920 |00:00:00.04 |    8462 |   3669 |       |       |          |
    |   4 |     NESTED LOOPS             |                  |      1 |      1 |      0 |00:00:00.01 |       6 |      1 |       |       |          |
    |   5 |      NESTED LOOPS            |                  |      1 |      1 |      0 |00:00:00.01 |       6 |      1 |       |       |          |
    |   6 |       INDEX FULL SCAN        | I_FILE2          |      1 |      3 |      3 |00:00:00.01 |       1 |      0 |       |       |          |
    |*  7 |       TABLE ACCESS CLUSTER   | FET$             |      3 |      1 |      0 |00:00:00.01 |       5 |      1 |       |       |          |
    |*  8 |        INDEX UNIQUE SCAN     | I_TS#            |      3 |      1 |      3 |00:00:00.01 |       2 |      0 |       |       |          |
    |*  9 |      TABLE ACCESS CLUSTER    | TS$              |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
    |* 10 |       INDEX UNIQUE SCAN      | I_TS#            |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
    |  11 |     NESTED LOOPS             |                  |      1 |     16 |     16 |00:00:00.01 |      19 |      7 |       |       |          |
    |  12 |      NESTED LOOPS            |                  |      1 |     16 |     16 |00:00:00.01 |      15 |      7 |       |       |          |
    |* 13 |       TABLE ACCESS FULL      | TS$              |      1 |      2 |      3 |00:00:00.01 |       6 |      1 |       |       |          |
    |* 14 |       FIXED TABLE FIXED INDEX| X$KTFBFE (ind:1) |      3 |     10 |     16 |00:00:00.01 |       9 |      6 |       |       |          |
    |* 15 |      INDEX UNIQUE SCAN       | I_FILE2          |     16 |      1 |     16 |00:00:00.01 |       4 |      0 |       |       |          |
    |  16 |     NESTED LOOPS             |                  |      1 |    904 |    904 |00:00:08.75 |    8135 |   3658 |       |       |          |
    |* 17 |      HASH JOIN               |                  |      1 |    904 |    904 |00:00:08.74 |    8131 |   3656 |  1087K|  1087K| 1288K (0)|
    |* 18 |       HASH JOIN              |                  |      1 |    102 |    102 |00:00:00.01 |      10 |      0 |  1483K|  1483K|  913K (0)|
    |* 19 |        TABLE ACCESS FULL     | TS$              |      1 |      2 |      3 |00:00:00.01 |       6 |      0 |       |       |          |
    |* 20 |        TABLE ACCESS FULL     | RECYCLEBIN$      |      1 |    102 |    102 |00:00:00.01 |       4 |      0 |       |       |          |
    |* 21 |       FIXED TABLE FULL       | X$KTFBUE         |      1 |   1000 |   7548 |00:00:00.13 |    8121 |   3656 |       |       |          |
    |* 22 |      INDEX UNIQUE SCAN       | I_FILE2          |    904 |      1 |    904 |00:00:00.01 |       4 |      2 |       |       |          |
    |  23 |     NESTED LOOPS             |                  |      1 |      1 |      0 |00:00:00.01 |     302 |      3 |       |       |          |
    |  24 |      NESTED LOOPS            |                  |      1 |      1 |      0 |00:00:00.01 |     302 |      3 |       |       |          |
    |  25 |       MERGE JOIN CARTESIAN   |                  |      1 |    306 |    306 |00:00:00.01 |       5 |      1 |       |       |          |
    |  26 |        INDEX FULL SCAN       | I_FILE2          |      1 |      3 |      3 |00:00:00.01 |       1 |      0 |       |       |          |
    |  27 |        BUFFER SORT           |                  |      3 |    102 |    306 |00:00:00.01 |       4 |      1 |  6144 |  6144 | 6144  (0)|
    |* 28 |         TABLE ACCESS FULL    | RECYCLEBIN$      |      1 |    102 |    102 |00:00:00.01 |       4 |      1 |       |       |          |
    |  29 |       TABLE ACCESS CLUSTER   | UET$             |    306 |      1 |      0 |00:00:00.01 |     297 |      2 |       |       |          |
    |* 30 |        INDEX UNIQUE SCAN     | I_FILE#_BLOCK#   |    306 |      1 |     91 |00:00:00.01 |     206 |      2 |       |       |          |
    |* 31 |      TABLE ACCESS CLUSTER    | TS$              |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
    |* 32 |       INDEX UNIQUE SCAN      | I_TS#            |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |
    -----------------------------------------------------------------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------------------------------------------------------------------------
    
       7 - filter("F"."FILE#"="FI"."RELFILE#")
       8 - access("F"."TS#"="FI"."TS#")
       9 - filter("TS"."BITMAPPED"=0)
      10 - access("TS"."TS#"="F"."TS#")
      13 - filter(("TS"."CONTENTS$"=0 AND "TS"."BITMAPPED"<>0 AND INTERNAL_FUNCTION("TS"."ONLINE$")))
      14 - filter(("TS"."TS#"="KTFBFETSN" AND INTERNAL_FUNCTION("CON_ID")))
      15 - access("KTFBFETSN"="FI"."TS#" AND "KTFBFEFNO"="FI"."RELFILE#")
      17 - access("KTFBUESEGTSN"="RB"."TS#" AND "KTFBUESEGFNO"="RB"."FILE#" AND "KTFBUESEGBNO"="RB"."BLOCK#")
      18 - access("TS"."TS#"="RB"."TS#")
      19 - filter(("TS"."CONTENTS$"=0 AND "TS"."BITMAPPED"<>0 AND INTERNAL_FUNCTION("TS"."ONLINE$")))
      20 - filter(("RB"."TS#" IS NOT NULL AND "RB"."FILE#" IS NOT NULL AND "RB"."BLOCK#" IS NOT NULL))
      21 - filter(("CON_ID"=0 OR "CON_ID"=3))
      22 - access("RB"."TS#"="FI"."TS#" AND "KTFBUEFNO"="FI"."RELFILE#")
      28 - filter(("RB"."TS#" IS NOT NULL AND "RB"."FILE#" IS NOT NULL AND "RB"."BLOCK#" IS NOT NULL))
      30 - access("U"."TS#"="RB"."TS#" AND "U"."SEGFILE#"="RB"."FILE#" AND "U"."SEGBLOCK#"="RB"."BLOCK#")
           filter(("U"."TS#"="FI"."TS#" AND "U"."SEGFILE#"="FI"."RELFILE#"))
      31 - filter("TS"."BITMAPPED"=0)
      32 - access("TS"."TS#"="U"."TS#")
    
    Note
    -----
       - statistics feedback used for this statement
       - this is an adaptive plan
    

    In step 17 wird der NESTED LOOPS Join durch einen HASH JOIN ersetzt, so dass der kostspielige Zugriff auf X$KTFBUE nur einmalig erfolgt (außerdem sieht man die durch das statistics feedback hervorgerufenen Cardinality-Korrekturen). Mit dem adaptiven Plan ist das Problem also nach kurzer Zeit keines mehr. Für ältere Releases bleibt aber festzuhalten, dass ein mit vielen Objekten gefüllter Recyclebin negative Auswirkungen auf die Query-Performance für Abfragen gegen DBA_FREE_SPACE hat. Das Problem ist, wie schon erwähnt, nicht unbekannt - nachdem ich mein Ergebnis hatte, habe ich bei Martin Widlake einen Artikel gleichen Inhalts gefunden (und es gibt weitere Artikel ähnlichen Inhalts). Weniger bekannt dürfte aber sein, dass die Schwierigkeit in 12c für alle praktischen Fragen als behoben betrachtet werden kann.

    Dienstag, April 22, 2014

    Speichernutzungsdetails in V$PROCESS_MEMORY_DETAIL

    Im Rahmen seiner epischen Oracle Memory Troubleshooting Reihe (die im Jahr 2009 begann) erläutert Tanel Poder die verschiedenen Informationen zur PGA-Nutzung der Prozesse, die die dynamischen Performance-Views zur Verfügung stellen:
    • v$process: liefert in den Spalten pga_used_mem und pga_alloc_mem einen (in vielen Fällen bereits ausreichenden) Überblick über die PGA Nutzung.
    • v$process_memory: liefert Informationen zur Verteilung dieser Ressourcennutzung auf die Bereiche SQL, PL/SQL, Java, Unused(Freeable) und Other.
    • für den Bereich SQL liefert v$sql_workarea_active Details zur Verteilung des Speicherverbrauchs auf einzelne Schritte in den Ausführungsplänen.
    • für den Bereich "Other" ist die Analyse etwas komplizierter und erforderte früher die Erzeugung eines PGA/UGA memory heapdump (mit Hilfe von oradebug oder durch Aktivierung eines entsprechenden Events für die Session).
    • Seit 10.2 liefert v$process_memory_detail die benötigten Details. Allerdings wird diese View nur auf ausdrückliche Aufforderung gefüllt (oradebug dump pga_detail_get) - und erst dann, wenn der zugehörige Prozess wieder aktiv wird.
    • Die in der Regel eher kryptischen name und heap_name Angaben aus v$process_memory_detail lassen sich dann (hoffentlich) über eine MOS-Suche auflösen.
    • auf OS-Ebene kann zusätzlich pmap -x verwendet werden.
    Mit diesem Vorgehen wird die Ermittlung der Informationen sehr einfach - ihre Interpretation bleibt aber auf ergänzende Erläuterungen (aus MOS oder anderer Quelle) angewiesen: ich zumindest kann mit Namen wie "kxsc: kkspsc0 2" erst einmal wenig anfangen.

    Sonntag, Januar 12, 2014

    Informationen zu den X$-Tabellen

    Tanel Poder hat dieser Tage offenbar eine Artikelserie zu den X$-Tabellen begonnen - so jedenfalls deute ich die Angabe Part 1 im Namen des ersten Artikels -, die den dynamischen Performance-Views (V$) zugrunde liegen:
    • Oracle X$ tables – Part 1 – Where do they get their data from? erklärt, dass die übliche Interpretation der X$-Objekte als Repräsentation von Memory-Strukturen nicht falsch, aber auch nicht ganz vollständig ist. Für manche Objekte (z.B. x$ksuse unter v$session) ist die Beziehung tatsächlich unmittelbar und die Adressangabe in der x$-Tabelle verweist direkt auf einen zugehörigen Speicherort in der SGA, aber in anderen Fällen (etwa x$kcccp) führt der Adress-Verweis in die UGA, weil interne Helper-Funktionen die erforderlichen Daten dorthin kopieren. In diesen Fällen ist demnach zusätzliche Arbeit zur Präsentation der Daten erforderlich.
    Die hoffentlich folgenden Artikel zum Thema werde ich hier gelegentlich ergänzen.

    Nachtrag 15.01.2014: Mit Verweis auf Tanel Poders Artikelserie hat Jonathan Lewis in seinem Blog eine table function veröffentlicht, mit der sich bestimmen lässt, welche X$ Objekte auf welche Memory-Bereiche Bezug nehmen.

    Mittwoch, Juni 19, 2013

    Speicherplatzberechnung

    Im OTN Forum wurde zuletzt die Frage aufgeworfen, wie es dazu kommen kann, dass ein Tabellen-Segment deutlich kleiner ist als die Berechnung NUM_ROWS * AVG_ROW_LENGTH erwarten lassen würde, und Jonathan Lewis hat seine Erklärungen zum Thema im eigenen Blog aufgeführt. In der Regel dürften bei diesem Effekt die folgenden beiden Ursachen im Spiel sein:
    • compression: weil dbms_stats in diesem Fall die entpackten Längen ermittelt
    • lobs: weil dbms_stats immer von der maximal möglichen Größe eines lob locator ausgeht
    In meiner privaten Script-Sammlung gibt's ein Exemplar, das genau diese Berechnung durchführt, aber dabei den etwas nebulösen Hinweis ergänzt, dass ein niedriger Füllgrad auch Indiz für ein Statistik-Problem sein kann - und dieser Hinweis lässt sich jetzt etwas präzisieren.

    Dienstag, April 02, 2013

    Hinweise zu Hints

    Tanel Poder hat mal wieder einen extrem interessanten Artikel veröffentlicht: diesmal zu den nicht offiziell dokumentierten Views V$SQL_FEATURE und V$SQL_FEATURE_HIERARCHIE, zu denen bisher nur spärliche Hinweise (unter anderem) von Julian Dyke und Yong Huang zu finden waren. Außerdem liefert er ein paar Analyse-Scripts, mit denen sich diesen beiden Views und V$SQL_HINT einige interessante Informationen entnehmen lassen:
    • V$SQL_HINT
      • in diesem Fall beschränkt sich das zugehörige Script hint auf eine case-insensitive Suche über den Namen des Hints und eine Spaltenauswahl. Zu den enthaltenen Informationen gehören die Angaben des Releases, in dem der Hint zum ersten Mal erschien (VERSION), und des Releases, in dem der Hint in den "stored outline" Code aufgenommen wurde (VERSION_OUTLINE).
    • V$SQL_FEATURE, V$SQL_FEATURE_HIERARCHIE
      • das Script hinth (Hint Hierarchie) verknüpft die beiden Views und liefert damit Informationen zur Feature-Hierarchy und zeigt, in welchen Kontext ein Hint gehört (Execution oder Compilation; darunter dann CBO, RBO, QUERY_REWRITE, TRANSFORMATION etc.)
      • das Script sqlfh (SQL feature hierarchy) liefert eine Auflösung diverser Abkürzungen wie CBQT (cost-based query transformation) oder CVM (complex view merging), die man auch aus dem CBO Trace kennt (Event 10053)
    Die direkten Links auf die Scripts des Herrn Poder spare ich mir, da ich mir bei Bedarf ähnliche Queries basteln würde - und denen würde ich dann Namen geben, mit denen ich mehr anfangen kann, als mit den Poder'schen Kürzeln.

    Als kleine Ergänzung noch ein Link zu einem älteren Artikel von Jonathan Lewis mit einer kurzen Taxonomie undokumentierter Hints - die man in der Regel natürlich mit noch mehr Vorsicht behandeln sollte als die dokumentierten Varianten (mit denen man bekanntlich auch schon vorsichtig sein sollte).

    Mittwoch, März 06, 2013

    SQL_ID ohne SQL_TEXT

    Julian Dontcheff liefert eine Erklärung für das Phänomen, dass in V$SQL SQL_ID-Angaben erscheinen, zu denen V$SQLTEXT keinen Text liefern kann:
    These are Oracle internal or so called pseudo cursors which are just structures providing direct access database objects by avoiding the overhead of writing an explicit SELECT statement. These structures access directly most of the time the data dictionary tables, CLOBs and BLOBs. Sometimes even NCHAR and NVARCHAR2.
    Zu diesen SQL_IDs gibt es also tatsächlich keine Texte, weil es sich nicht um SQL-Statements handelt. Im Artikel wird (mit Verweis auf ein MOS-Dokument) auch noch eine View-Definition geliefert, mit deren Hilfe ein paar Informationen zu den Zugriffen zusammengestellt werden können.

    Samstag, Februar 09, 2013

    Sichtbarkeit von dynamic sampling

    Dom Brooks weist darauf hin, dass man der OTHER_XML-Spalte in V$SQL_PLAN entnehmen kann, ob für eine Query dynamic sampling verwendet wurde, wobei man in 11.1 nur eine ja/nein-Angabe und in 11.2 das sampling level findet. Außerdem liefert er Queries zum Auslesen der XML-Werte. Die OTHER_XML-Spalten diverser dictionary views mit Plan-Bezug enthalten eine ganze Menge wichtiger Informationen (Bindewerte, Outline-Hints etc.), aber mir persönlich wäre es lieber, wenn man dafür eine andere Repräsentation gewählt hätte.

    Montag, Februar 04, 2013

    Bestimmung der compression von Tabellen

    Heute wurde im OTN Forum die Frage gestellt: woran kann man erkennen, ob die Inhalte einer Partition komprimiert sind? Ich denke, das ist eine ziemlich interessante Frage, und eine, auf die Jonathan Lewis in einem Kommentar zu einem seiner Artikel zum Thema compression auf der AllThingsOracle-Seite vor kurzem eine kompakte Antwort gegeben hat:
    Q: Is there a way to tell if the data is actually compressed?
    A: In effect, no. 
    You might be able to make a reasonable guess - check the average rows per block (num_rows / blocks), then cross-check with the avg_row_len (which reports the uncompressed row length, even when the rows are compressed). You could also use dbms_rowid to convert rowids to file and block numbers and check the number of rows per block individually - because you might have a table where half the data was compressed and half wasn't. 
    In principle there is a method, mentioned originally in the 9.2 manuals I think. Column spare1 of table sys.seg$ has two bits set to show whether or not a segment is compressed and whether it holds any compressed data, so you could query seg$ (joined to various other data dictionary tables) to check. The tests are: bitand(spare1, 2048) = 2048 to check if the segment is defined with compression, and bitand(spare1, 4096) = 4096 to check if any of the data is compressed - unfortunately when I did a quick check of bit 4096 in a recent version of Oracle I found that it got set as soon as bit 2048 got set. Even it it worked, of course, all you could learn from a single bit was that some of the data in the table was compressed, you'd still have no idea about how much was compressed.
    Auf Anhieb könnte man meinen, dass der Fall viel einfacher läge, denn schließlich enthält dba_tab_partitions eine Spalte COMPRESSION (und eine weiter Spalte COMPRESS_FOR). Aber darin steht nur der für folgende Operationen gültige Status: zum aktuellen Zustand gibt es keine Aussage. Dazu ein Beispiel:

    drop table test_part_compress;
    
    create table test_part_compress ( 
        startdate date
      , col1 number
      , col2 varchar2(100) 
    )
    partition by range (startdate) (
        partition p1 values less than (to_date('01.01.2013','dd.mm.yyyy'))
      , partition p2 values less than (to_date('01.02.2013','dd.mm.yyyy'))
    ) tablespace test_ts;
    
    insert into test_part_compress
    select to_date('31.12.2012', 'dd.mm.yyyy') + mod(rownum, 2) startdate
         , mod(rownum, 10) col1
         , lpad('*', 100, '*') col2
      from dual
    connect by level <= 10000;
    
    exec dbms_stats.gather_table_stats(user, 'test_part_compress')
    
    select partition_name
         , num_rows
         , blocks
         , compression
         , compress_for
      from dba_tab_partitions
     where table_name = 'TEST_PART_COMPRESS';
    
    PARTITION_NAME                   NUM_ROWS     BLOCKS COMPRESS COMPRESS_FOR
    ------------------------------ ---------- ---------- -------- ------------
    P1                                   5000         84 DISABLED
    P2                                   5000         84 DISABLED
    
    alter table test_part_compress modify partition p1 compress;
    
    exec dbms_stats.gather_table_stats(user, 'test_part_compress')
    
    select partition_name
         , num_rows
         , blocks
         , compression
         , compress_for
      from dba_tab_partitions
     where table_name = 'TEST_PART_COMPRESS';
    
    PARTITION_NAME                   NUM_ROWS     BLOCKS COMPRESS COMPRESS_FOR
    ------------------------------ ---------- ---------- -------- ------------
    P1                                   5000         84 ENABLED  BASIC
    P2                                   5000         84 DISABLED
    
    alter table test_part_compress move partition p1;
    
    exec dbms_stats.gather_table_stats(user, 'test_part_compress')
    
    select partition_name
         , num_rows
         , blocks
         , compression
         , compress_for
      from dba_tab_partitions
     where table_name = 'TEST_PART_COMPRESS';
    
    PARTITION_NAME                   NUM_ROWS     BLOCKS COMPRESS COMPRESS_FOR
    ------------------------------ ---------- ---------- -------- ------------
    P1                                   5000          8 ENABLED  BASIC
    P2                                   5000         84 DISABLED
    
    alter table test_part_compress modify partition p1 nocompress;
    
    select partition_name
         , num_rows
         , blocks
         , compression
         , compress_for
      from dba_tab_partitions
     where table_name = 'TEST_PART_COMPRESS';
    
    PARTITION_NAME                   NUM_ROWS     BLOCKS COMPRESS COMPRESS_FOR
    ------------------------------ ---------- ---------- -------- ------------
    P1                                   5000          8 DISABLED
    P2                                   5000         84 DISABLED
    

    Die Ergebnisse zeigen, dass die Angabe der COMPRESS oder NOCOMPRESS-Eigenschaft zunächst keine direkte Wirkung auf das vorliegende Segment hat: erst ein folgendes MOVE führt zur physikalischen Reorganisation. Das bedeutet dann auch, dass man im Fall der Partition P1 am Ende des Tests nicht ohne Weiteres bestimmen kann, ob ihr Inhalt komprimiert ist. Zur Bestimmung bleiben also die Vorschläge des Herrn Lewis. Dabei lasse ich die Prüfungen zur Größe außen vor, denn im Test ist der Unterschied zwischen 8 Blocks und 84 Blocks ausreichend deutlich:

    select partition_name
         , header_file
         , header_block
      from dba_segments
     where segment_name = 'TEST_PART_COMPRESS';
    
    PARTITION_NAME                 HEADER_FILE HEADER_BLOCK
    ------------------------------ ----------- ------------
    P1                                       5       147272
    P2                                       5       147104
    
    select block#
         , blocks
         , bitand(spare1, 2048) compression_defined
         , bitand(spare1, 4096) compressed
      from sys.seg$ t
     where file# = 5
       and BLOCK# in (147104, 147272)
    
    BLOCK#     BLOCKS COMPRESSION_DEFINED COMPRESSED
    ------ ---------- ------------------- ----------
    147104         88                   0          0
    147272         16                   0       4096
    

    Das scheint also exakt die gewünschte Information zu liefern. Eine andere Variante zur Bestimmung nennt Randolf Geist in seinem Beitrag zum OTN-Thread: die Verwendung der Funktion dbms_compression.get_compression_type:

    select subobject_name
         , data_object_id
      from dba_objects
     where object_name = 'TEST_PART_COMPRESS'
       and subobject_name is not null;
    
    SUBOBJECT_NAME                 DATA_OBJECT_ID
    ------------------------------ --------------
    P1                                     101978
    P2                                     101977
    
    select dbms_rowid.rowid_relative_fno(rowid) file_nr
         , dbms_compression.get_compression_type(user, 'TEST_PART_COMPRESS', rowid) compression_type
         , count(*) cnt
      from test_part_compress
     group by dbms_rowid.rowid_relative_fno(rowid)
            , dbms_compression.get_compression_type(user, 'TEST_PART_COMPRESS', rowid);
    
    OBJECT COMPRESSION_TYPE        CNT
    ------ ---------------- ----------
    101978                2       5000
    101977                1       5000
    

    Zumindest für den aktuell erreichten Zustand einer komprimierten Partition, deren Status aktuell NOCOMPRESS ist, sind die Tests demnach aussagekräftig: Für P1 wird der COMPRESSION_TYPE = 2 angegeben, der anscheinend für Basic und OLTP compression steht (sagt jedenfalls Kerry Osborne). Der Vollständigkeit halber hier noch ein paar weitere Schritte in die eingeschlagene Richtung:

    -- move nach NOCOMPRESS-Definition
    alter table test_part_compress move partition p1;
    exec dbms_stats.gather_table_stats(user, 'test_part_compress')
    
    PARTITION_NAME                   NUM_ROWS     BLOCKS COMPRESS COMPRESS_FOR
    ------------------------------ ---------- ---------- -------- ------------
    P1                                   5000         74 DISABLED
    P2                                   5000         84 DISABLED
    
    -- beide Partitionen liefern jetzt COMPRESSION_TYPE = 1 (keine compression)
    OBJECT COMPRESSION_TYPE        CNT
    ------ ---------------- ----------
    101979                1       5000
    101977                1       5000
    
    BLOCK#     BLOCKS COMPRESSION_DEFINED COMPRESSED
    ------ ---------- ------------------- ----------
    147096         80                   0          0
    147104         88                   0          0
    
    -- erneute COMPRESS-Definition
    alter table test_part_compress modify partition p1 compress;
    exec dbms_stats.gather_table_stats(user, 'test_part_compress')
    
    PARTITION_NAME                   NUM_ROWS     BLOCKS COMPRESS COMPRESS_FOR
    ------------------------------ ---------- ---------- -------- ------------
    P1                                   5000         74 ENABLED  BASIC
    P2                                   5000         84 DISABLED
    
    -- der COMPRESSION_TYPE bleibt 1, was plausibel ist, da die Daten ja tatsächlich nicht komprimiert sind
    OBJECT COMPRESSION_TYPE        CNT
    ------ ---------------- ----------
    101979                1       5000
    101977                1       5000
    
    -- aber seg$ behauptet, dass die Partition jetzt als COMPRESSed definiert ist und ihre Sätze komprimiert sind,
    -- was nicht den Tatsachen entspricht
    BLOCK#     BLOCKS COMPRESSION_DEFINED COMPRESSED
    ------ ---------- ------------------- ----------
    147096         80                2048       4096
    147104         88                   0          0
    

    Auf Anhieb scheint dbms_compression.get_compression_type im gegebenen Fall also die brauchbarere Information zu liefern - und immerhin gibt es überhaupt Möglichkeiten, die unzureichende Aussage aus dba_tab_partitions zu ergänzen.

    Freitag, Dezember 21, 2012

    TEMP usage Angabe in ASH

    Eine kurze Notiz, da ich in letzter Zeit häufiger nach historischen Informationen zur Ressourcennutzung gesucht habe, die es (noch) nicht in ASH bzw. AWR gab: Marc Fielding weist darauf hin, dass DBA_HIST_ACTIVE_SESS_HISTORY ab 11.2 eine Spalte TEMP_SPACE_ALLOCATED enthält ("Amount of TEMP memory (in bytes) consumed by this session at the time this sample was taken"). Das sollte die nachträgliche Analyse entsprechender Probleme massiv erleichtern.

    Donnerstag, Dezember 13, 2012

    V$SQLFN_METADATA

    Carsten Czarski weist in seinem Blog auf zwei interessante dynamische Performance-Views hin:
    • V$SQLFN_METADATA: "contains metadata about operators and built-in functions. Note that this view does not contain information about arguments because the number of arguments will be different for various functions. Information about arguments is contained in V$SQLFN_ARG_METADATA, which can be joined with V$SQLFN_METADATA to get information about any function and its arguments."
    • V$SQLFN_ARG_METADATA: "contains metadata about function arguments. There is one row for each argument of every function found in V$SQLFN_METADATA. There are no rows for functions that do not have any arguments."
    Aus V$SQLFN_METADATA geht unter anderem hervor, ob es sich bei einer Funktion um eine analytische oder eine Aggregatfunktion handelt und seit welchem Release die Funktion (bzw. der Operator) im Oracle Server verfügbar ist.

    Mittwoch, August 29, 2012

    Recursive Sessions

    Im Rahmen seiner jüngsten Oracle Hacking Session zum Thema Oracle Parameter Infrastructure (deren Kernpunkte ich hier vielleicht gelegentlich auch noch ausführen werde) weist Tanel Poder auf einen Artikel Recursive Sessions and ORA-00018: maximum number of sessions exceeded hin, dessen Veröffentlichung ich übersehen (oder schon wieder vergessen) hatte.

    Darin erklärt er, dass Oracle im Rahmen von DDL-Operationen rekursive Sessions startet, die dazu führen können, dass ein DDL-Kommando in einer bereits angemeldeten Session den (ohne diese Information verblüffenden) Fehler "ORA-00018: maximum number of sessions exceeded" hervorruft. Die rekursiven Sessions erscheinen nicht in V$SESSION, werden aber in V$RESOURCE_LIMIT berücksichtigt.