Mittwoch, September 28, 2011

Partition Exchange + Indizierung

Über Partition Exchange hatte ich hier vor einiger Zeit etwas geschrieben, musste mich dieser Tage aber noch mal etwas ernsthafter mit dem Thema beschäftigen, und kam dabei zu folgenden Ergebnissen:
  • Wenn man im "ALTER TABLE ... EXCHANGE PARTITION ... WITH TABLE ..." keine Klausel "INCLUDING INDEXES" angibt, dann werden alle auf der Austauschtabelle aufgebauten Indizes beim Exchange UNUSABLE.
  • Beim Aufbau von Indizes muss man den Grad der COMPRESSION berücksichtigen, da man sonst auf "ORA-28665: Tabelle und Partition müssen dasselbe Komprimierungsattribut haben" trifft. Der Fehlertext ist somit also etwas unpräzise, da auch Indizes berücksichtigt werden müssen.
Und noch eine harmlose Ergänzung: zum Monitoring des Stands der Index-Erzeugung kann man neben dem SQL-Monitor, v$session_longopsund den workare-Views auch noch dba_segments heranziehen:

select *
  from dba_segments
 where segment_type = 'TEMPORARY';

liefert Informationen zur Größe des im Aufbau befindlichen Index, der zunächst als temporäres Objekt mit einer technischen Id erzeugt wird, ehe daraus dann das permanente Segment wird.

Iggy Fernandez über DB-Design und Features

Die Überschriften der Einträge hier werden allmählich unübersichtlicher ...

Iggy Fernandez hat zuletzt einige interessante Artikel geschrieben, die ich hier summarisch verlinke:
Wenn der Herr Fernandez seine Drohung wahr macht und tatsächlich 40 Lessons liefert, werde ich mir die Links aber vermutlich irgendwann sparen ...

Montag, September 26, 2011

EXISTS im SELECT

Rob van Wijk, dessen Blog ich - wie ich vielleicht schon mal erwähnt habe - sehr schätze, hat mal wieder etwas Interessantes angesprochen: man kann EXISTS nicht nur im WHERE, sondern in einer searched case expression auch im SELECT verwenden, was in bestimmten Fällen effizienter sein kann als ein entsprechender OUTER JOIN. Wie immer beim Herrn van Wijk gibt's aussagekräftige Beispiele und eine einleuchtende Erklärung des Verhaltens.

Sonntag, September 25, 2011

Index Rebuilds und der Clustering Factor

Nächstes Quiz des Herrn Foote, diesmal zur Frage, wann ein Index Rebuild den Clustering Factor ändern kann. Im Grunde ist die Antwort: nie, aber es gibt doch ein paar Spezialfälle, die man als Ausnahmen von der Regel betrachten kann. Grundsätzlich gilt:
So for an index rebuild to actual have an impact on the CF on an index, means either the rows in the table needs to change or the order of the index entries needs to change.
However, when we typically rebuild an index, it has no impact at all on the table and so can’t possibly change the order of the rows there. Additionally, no matter how fragmented or inefficient the index structure might be, an index rebuild doesn’t change the order of the index entries either as they’re always sorted within the index in the order of the indexed columns.
Die Ausnahmen der  Regel sind Indizes, die von REVERSE auf NOREVERSE oder umgekehrt umgestellt werden - was aus meiner Sicht aber eigentlich etwas Anderes ist als ein einfacher Neuaufbau. Ein anderer Spezialfall wäre die Definitionsänderung eines FBI - aber auch das ist kein einfaches Rebuild. Damit kann man für alle praktischen Fälle wohl doch bei der Regel bleiben, dass nur ein Neuaufbau der Tabelle den Clustering Factor eines Index ändern kann.

Freitag, September 23, 2011

Oracle 11.2.0.3

Greg Rahn weist darauf hin, dass das Patchset 11.2.0.3 für diverse Platformen veröffentlicht wurde. Laut New Feature Guide liegen die Verbesserungen vor allem in den Bereichen ACFS, XML und OWB.

Recovery Area

Uwe Hesse erläutert in seinem Blog sehr ausführlich, warum eine Recovery Area empfehlenswert ist. Wenn ich mich mal wieder als DBA ausgebe, wäre das ein Thema, das intensivere Beschäftigung verdiente.

Rebuild Partitioned Index

Da ich die Syntax mit schöner Regelmäßigkeit anderswo suche, schreibe ich sie mal hier auf: um eine Index-Partition UNUSABLE zu setzen bzw. wieder neu aufzubauen, kann man folgende Kommandos verwenden:

alter index ... modify partition ... unusable;
alter index ... rebuild partition ...;

Alternativ kann man auch alle lokalen Indizes einer Partition auf einmal auf UNUSABLE setzen bzw. neu aufbauen:

alter table ... modify partition ... unusable local indexes;
alter table ... modify partition ... rebuild unusable local indexes;

Ein kleines Beispiel zum Thema liefert Tom Kyte.

Leider kann man einen als UNIQUE definierten Index vor einem Massendaten-Ladevorgang nicht über diesen Weg deaktivieren, um ihn später wieder aufzubauen, da das INSERT dann gegen einen ORA-01502 ("index 'string.string' or partition of such index is in unusable state") läuft - Details zu diesem Problem finden sich hier.

Donnerstag, September 22, 2011

Lebenszeit von Tablespace-Quotas

In Stefan Oehrlis Blog findet sich ein interessanter Hinweis: wenn man einen Tablespace löscht und später wieder einen gleichnamigen TS anlegt, dann werden Quotas, die für den alten TS galten auch wieder für den neuen wirksam. Klingt für mich wie eine fragwürdige Konstruktion.

Mittwoch, September 21, 2011

dbms_xplan.display_cursor und plan statistics

Kyle Haileys Blog ist auch eine dieser Ressourcen, aus denen man eigentlich jeden Beitrag verlinken kann... Diesmal liefert der Herr Hailey eine detaillierte Beschreibung der display_cursor-Routine aus dbms_xplan, erläutert den Hint gather_plan_statistics und weitere Möglichkeiten, die erweiterten row source execution stats zu aktivieren, und liefert schließlich eine Query, mit der man aus v$sql_plan_statistics_all die Hinweise auf massive Abweichungen zwischen E-Rows und A-Rows komfortable visualisieren kann.

SSAS Maestros

Microsoft hat eine erste Gruppe von SSAS-Spezailisten mit dem Adelsprädikat eines SSAS-Maestros ausgezeichnet. Bei der Titelgebung solcher Auszeichnungen scheint Kreativität gefragt zu sein - mal sehen, wer als erster seine Erzmagier, Diakone und Kardinäle benennt. Immerhin liefert mir der Beitrag ein oder zwei neue Blogs für meinen Reader.

Montag, September 19, 2011

Abhängigkeit von Features vom STATISTICS_LEVEL

Christian Antognini weist in seinem Blog auf die Abhängigkeit der Erhebung diverser elaborierter Statistiken vom STATISTICS_LEVEL hin. Neben den in V$STATISTICS_LEVEL aufgeführten Statistiken sind seinen Beoachtungen nach auch Cardinality Feedback und Adaptive Cursor Sharing vom STATISTICS_LEVEL abhängig.

Samstag, September 17, 2011

Deutschsprachige Oracle-Webseiten

Hier mal der Versuch, jene Liste von deutschsprachigen Oracle-Ressourcen zu erstellen, die ich schon häufiger anderswo gesucht habe.

Nachtrag 10.12.2014: mag sein, dass wir uns im Herbst des Zeitalters der Blogs befinden; jedenfalls sind viele der Seiten, die ich hier vor mehr als drei Jahren gesammelt habe, inzwischen sehr still geworden - einige gibt es auch nicht mehr. Zumindest die aktuelle Erreichbarkeit der Ressourcen habe ich deshalb noch mal überprüft und nachgetragen.

Blogs
ohne besondere Ordnung
Blog-Aggregatoren
  • Deutschsprachige DBA Community: Deutschsprachige DBA Community, Tipps und Tricks rund um die Oracle Datenbank (von Mitarbeitern von Oracle).
  • Deutschsprachige APEX und PL/SQL Community: Deutschsprachige Application Express Community: Tipps und Tricks zum Umgang mit APEX, SQL und PL/SQL (von Mitarbeitern von Oracle).
  • DOAG BLOG Aggregator: ein Aggregator, der diverse der oben genannten Blogs zusammenfasst.
  • German Oracle Blog Aggregator: ein Blog-Aggregator von Norbert Henz, der ebenfalls einige der bereits genannten Blogs - nun ja: aggregiert.
  • Trivadis: Gruppen-Blog der Trivadis Mitarbeiter zu Oracle, SQL Server, Sharepoint und vielen anderen Themen.
Foren
Webseiten
Die Liste bleibt relativ kurz, denn die meisten renommierten deutschsprachigen Oracle-Spezialisten schreiben englischsprachige Blogs. Extrem alte Webseiten, die offensichtlich nicht mehr aktualisiert werden, habe ich nicht berücksichtigt.

Data Warehouse Architektur

Thomas Kejser formuliert in seinem Blog einen interessanten Architekturvorschlag für Data Warehouses, der für mich wie eine behutsame Weiterentwicklung der Kimballschen Überlegungen aussieht. Man findet dort auch noch einen Link zum Kimball Forum, wo die Frage von Normalisierung/Denormalisierung im DWH-Kontext offenbar immer noch als Glaubenskrieg diskutiert wird.

Ebenfalls aus dem Microsoft-BI-Kosmos stammen die Überlegungen, die der Data Warehouse Junkie Marcel Franke in seinem Blog entwickelt.

ORADEBUG als Sicherheitsrisiko

Alexander Kornbrust erläutert in seinem Blog, welche - im Hinblick auf Security-Fragen gefährlichen - Optionen ORADEBUG zur Verfügung stellt.

Freitag, September 16, 2011

Row Cache Lock

Dieser Tage ist mir folgendes Phänomen begegnet: zu einer großen Tabelle existierte ein Index für die Spalten des Primary Keys, wobei der Primary Key selbst disabled war. Ohne intensiver darüber nachzudenken, hatte ich angenommen, dass die Aktivierung des PKs sich darauf beschränken würde, zu prüfen, ob ein passender Index exisitiert - aber offenbar ist anschließend noch mehr zu tun. Meine Vermutung war, dass ein weiterer FTS stattfinden müsste, aber ein Blick in v$session_longops konnte das nicht bestätigen. Genauer gesagt konnte v$session_longops gar nichts bestätigen oder widerlegen, da die Abfrage von der Session, in der die PK-Aktivierung lief, geblockt wurde. Das Verhalten lässt sich mit einem einfachen Test stabil reproduzieren (hier in 11.1.0.7):

-- Löschung und Neuanlage einer Testtabellen mit 10M rows
drop table big_t purge;

create table big_t
as
with
base_data
as
(
select rownum id1
  from dual
connect by level <= 1000000
)
,
mult
as
(
select rownum id2
  from dual
connect by level <= 10
)
select rownum id
     , 'aaaaaaaaa' col2
  from mult
     , base_data;

alter table big_t modify id not null;

create index big_t_idx on big_t(id);

alter table big_t add constraint big_t_pk primary key (id) disable;

alter table big_t enable constraint big_t_pk;

Tabelle wurde geändert.

Abgelaufen: 00:00:17.26

Ein gleichzeitiger Zugriff einer zweiten Session auf v$session_longops (nicht aber auf viele andere v$-Views) wird blockiert, und in einer dritten Session kann man dazu in v$session_wait folgende Informationen finden:

SID       SEQ# EVENT            
--- ---------- -----------------
140        110 row cache lock   

In Dion Chos (auch sonst sehr lesenswertem) Blog findet man eine Erläuterung dazu, wie man den holder eines row cache locks mit Hilfe von v$rowcache_parent ermitteln kann, aber in meinem Fall genügte ein Blick in v$session, um die blocking_session zu bestimmen. In der Dokumentation habe ich dafür zwar die Kurzbeschreibung "The session is trying to get a data dictionary lock" gefunden, aber keine grundsätzlichere Erklärung (kann auch sein, dass mein Suche nicht besonders systematisch war). Offenbar handelt es sich um ein Lock im dictionary cache, aber wieso es für den v$session_longops-Zugriff relevant ist aber nicht für Zugriffe auf ähnliche Objekte, kann ich nicht sagen.

Was ich immerhin herausgefunden habe, ist, dass die PK-Aktivierung ohne Verzögerung erfolgt, wenn der Index zur PK-Unterstützung als UNIQUE definiert ist, also:

create unique index big_t_idx on big_t(id);

Das ist durchaus einleuchtend - offenbar muss die Eindeutigkeit im Fall des non-unique Index erst explizit geprüft werden -, erklärt die Wirkung des "row cache lock" aber noch nicht. Wenn jemand mehr darüber weiß, würde ich mich über entsprechende Hinweise freuen.

Nachtrag 19.09.2011: Das row cache lock tritt offenbar auch bei der nachträglichen Definition eines NOT NULL Constraints auf.

Montag, September 12, 2011

DBMS_STATS.DISPLAY - Outline Hints und Aliase

Da ich es sonst immer wieder anderswo nachschlagen muss, schreib ich endlich mal auf, wie man sich die Outline Hints und Subquery-Aliase einer Query mit Hilfe von DBMS_STATS.DISPLAY anzeigen lassen kann. Die Syntax dafür lautet einfach:

select *
  from table(dbms_xplan.display('', '', 'ALIAS OUTLINE'));

In den Aliasen findet man dann mit etwas Glück auch die Labels wieder, die man einem Query Block mit Hilfe des QB_NAME hints geben kann, der (einmal mehr) bei Jonathan Lewis ausführlicher beschrieben wurde.

System Statistic Bug in 11.2

Jonathan Lewis hat in seinem Blog einige der einschlägigen Hinweise auf den Bug 9842771 gesammelt, der sich darin äußert, dass in AUX_STAT$ abwegig hohe Werte für SREADTIM and MREADTIM erscheinen.

Freitag, September 09, 2011

Descending Indexes

Die nächste Quizfrage des Herrn Foote, diesmal zum Thema Descending Indexes. Ohne die Details hier alle noch mal nacherzählen zu wollen, ein paar wichtige Punkte:
  • ein DESC-Index ist vom Typ her FUNCTION-BASED NORMAL
  • beim Überlauf von Index-Blocks erfolgt ein 50/50 Spilt, was den Index weniger kompakt macht als einen ASC-Index, beim dem die Block-Splits 90/10 erfolgen (also eigentlich eher 99/1)
  • der RBO ignoriert neumodischen Kram wie FBIs
  • für einen Index mit nur einer Spalte ist die Definition als descending völlig witzlos, da sie Overhead hervorruft und Oracle einer entsprechenden ascending index ebenfalls zur Vermeidung der Sortierung heranziehen kann. Nur mehrspaltige compound Indizes sind potentiell mit desc (möglicherweise gemischt mit asc) sinnvoll definiert.
Nachtrag 14.09.2011: in den Kommentaren zum Artikel haben sich auch noch die Herren Geist und Lewis zu Wort gemeldet und noch ein paar erinneringswürdige Fakten beigesteuert:
  • descending indexes werden nicht für dynamic sampling in Betracht gezogen: "The reason for not using dynamic sampling on the descending index is also interesting: The dynamic sampling code uses the original predicates (between 42 and 84) and not the actual ones that are then applied to the descending index (the SYS_OP… stuff), so it obviously doesn’t consider the index as candidate for sampling." Randolf Geist
  • descending indexes sind pro row ein byte größer als entsprechend acs indexes: "the descending column is a one’s-bit complement of the original value, stored in ascending order, with a 0xff appended. This means the index will be one byte larger for each row for each descending column – and that’s a pretty good reason for not making every column in an index descending. (Plus there are cases – possibly fixed in 11.2 – where descending indexes won’t be used for plans which would be considered for ascending indexes.)" Jonathan Lewis
  • ein dritter Kommentar zeigt, dass ein ALTER TABLE SHRINK SPACE für Tabellen mit einem DESC Index nicht funktioniert (und einen "ORA-10631: SHRINK clause should not be specified for this object" liefert)

    Donnerstag, September 08, 2011

    Partitionierte Tabellen und direct path FTS

    Über das in 11g eingeführte Feature der direct path reads für serial table scans hatte ich schon mehrfach gelesen, ohne mir viel dabei zu denken. Vor kurzem habe ich dann in Tanel Poders Hacking Session Video den Hinweis bekommen, dass die für die Entscheidung der run time engine, ob eine Tabelle für den direct path Zugriff in Betracht gezogen wird, relevante _small_table_threshold pro Segment definiert ist - also auch für die Partitionen einer partitionierten Tabelle. Trotzdem war ich heute zunächst überrascht, als ein einfaches SELECT COUNT(*) auf einer Heap-Tabelle mit 5.000.000 Sätzen (etwa 1 GB groß) in ca. 20 sec. ablief, während der Zugriff auf eine identisch gefüllte partitionierte Tabelle mehr als 40 sec. benötigte. Ein Vergleich der Statistiken in v$sesstat half meinem Gedächtnis dann auf die Sprünge:

    NAME
                    TABLE_NO_PART
                  TABLE_PART
    consistent gets
    72298
    74445
    consistent gets - examination
    0
    819
    consistent gets direct
    72286
    0
    consistent gets from cache
    12
    74445
    consistent gets from cache (fastpath)
    12
    71726
    DB time
    1699
    5046
    free buffer inspected
    0
    73410
    free buffer requested
    0
    72568
    physical reads
    72286
    72568
    physical reads cache
    0
    72568
    physical reads cache prefetch
    0
    70674
    physical reads direct
    72286
    0
    table scan blocks gotten
    72286
    72444
    table scan rows gotten
    5637076
    5637107
    table scans (direct read)
    1
    0
    table scans (long tables)
    1
    0
    table scans (short tables)
    0
    86

    Die 86 table scans (short tables) gehören zu den Partitionen, während für die nicht partitionierte Tabelle ein table scan (long tables) erscheint - und dieser ruft dann consistent gets direct hervor, während die kleineren Partitionen über den Buffer Cache gelesen werden, was zumindest in diesem Fall deutlich langsamer ist. Die Entscheidung für den direct path Zugriff kann übrigens mit Hilfe des Parameters _serial_direct_read beeinflusst werden, der wie alle hidden parameter natürlich nur nach Rücksprache mit dem zuständigen Arzt oder Apotheker verwendet werden sollte.

    Montag, September 05, 2011

    Einzelsatzzugriff

    Und schon wieder eine Quizfrage des Herrn Foote:
    Assume you have a tiny little table with just 42 rows (naturally) that all fit in one table block. Order the following options in order of “efficiency” (most efficient option first) when accessing just one of these rows:
    1) Full Table Scan of Heap Table
    2) PK access of an Index Organised Table
    3) Index access of Heap Table via a Unique Index
    4) Index access of Heap Table via a Non-Unique Index

    In den Kommentaren wurden gleich ein paar berechtigte Nachfragen gestellt, aber ich tu mal so, als sei die Frage völlig harmlos:

    1. FTS

    create table t42 tablespace test_ts
    as
    select rownum id
         , 'Ziggy' col2
      from dual
    connect by level <= 42;
    
    exec dbms_stats.gather_table_stats(user, 'T42')
    
    select *
      from t42
     where id = 1;
    
       ID COL2
    ----- -----
        1 Ziggy
    
    --------------------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |     1 |     9 |     2   (0)| 00:00:01 |
    |*  1 |  TABLE ACCESS FULL| T42  |     1 |     9 |     2   (0)| 00:00:01 |
    --------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       1 - filter("ID"=1)
    
    
    Statistiken
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              4  consistent gets
              0  physical reads
              0  redo size
            599  bytes sent via SQL*Net to client
            520  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed

    2. non-unique Index

     
    create index t42_idx on t42(id);
    
    --> Query
    
    ---------------------------------------------------------------------------------------
    | Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
    ---------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |         |     1 |     9 |     2   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T42     |     1 |     9 |     2   (0)| 00:00:01 |
    |*  2 |   INDEX RANGE SCAN          | T42_IDX |     1 |       |     1   (0)| 00:00:01 |
    ---------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       2 - access("ID"=1)
    
    
    Statistiken
    ----------------------------------------------------------
              1  recursive calls
              0  db block gets
              3  consistent gets
              0  physical reads
              0  redo size
            603  bytes sent via SQL*Net to client
            520  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed

    3. Unique Index

     
    create unique index t42_uidx on t42(id);
    
    --> Query
    
    ----------------------------------------------------------------------------------------
    | Id  | Operation                   | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
    ----------------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT            |          |     1 |     9 |     1   (0)| 00:00:01 |
    |   1 |  TABLE ACCESS BY INDEX ROWID| T42      |     1 |     9 |     1   (0)| 00:00:01 |
    |*  2 |   INDEX UNIQUE SCAN         | T42_UIDX |     1 |       |     0   (0)| 00:00:01 |
    ----------------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       2 - access("ID"=1)
    
    
    Statistiken
    ----------------------------------------------------------
              1  recursive calls
              0  db block gets
              2  consistent gets
              0  physical reads
              0  redo size
            467  bytes sent via SQL*Net to client
            509  bytes received via SQL*Net from client
              1  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
     

    4. IOT-Zugriff

     
    create table t42 (id, col2, constraint t42pk primary key(id)) organization index tablespace test_ts
    as
    select rownum id
         , 'Ziggy' col2
      from dual
    connect by level <= 42
    
    exec dbms_stats.gather_table_stats(user, 'T42')
    
    --> Query
    
    ---------------------------------------------------------------------------
    | Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
    ---------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |       |     1 |     9 |     0   (0)| 00:00:01 |
    |*  1 |  INDEX UNIQUE SCAN| T42PK |     1 |     9 |     0   (0)| 00:00:01 |
    ---------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       1 - access("ID"=1)
    
    
    Statistiken
    ----------------------------------------------------------
              1  recursive calls
              0  db block gets
              1  consistent gets
              0  physical reads
              0  redo size
            599  bytes sent via SQL*Net to client
            520  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
     

    5. Single Table Hash Cluster


    Nicht in der Fragestellung enthalten, aber halbwegs zum Thema passend...

    create cluster t_cluster
    (id number(2,0))
    tablespace test_ts
    hash is id hashkeys 64;
    
    create table t42
    ( id number(2,0)
    , col2 varchar2(10) )
    cluster t_cluster (id);
    
    insert into t42
    select rownum id
         , 'Ziggy' col2
      from dual
    connect by level <= 42;
    
    --> Query
    
    ---------------------------------------------------------------
    | Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)|
    ---------------------------------------------------------------
    |   0 | SELECT STATEMENT  |      |     1 |     9 |     0   (0)|
    |*  1 |  TABLE ACCESS HASH| T42  |     1 |     9 |            |
    ---------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
       1 - access("ID"=1)
    
    
    Statistiken
    ----------------------------------------------------------
              0  recursive calls
              0  db block gets
              1  consistent gets
              0  physical reads
              0  redo size
            599  bytes sent via SQL*Net to client
            520  bytes received via SQL*Net from client
              2  SQL*Net roundtrips to/from client
              0  sorts (memory)
              0  sorts (disk)
              1  rows processed
    

    Also keine Überraschungen bei den Platzierungen:
    1. IOT (und HashCluster) mit 1 consistent get für den Zugriff auf einen einzelnen Datensatz (+ 1 Latch)
    2. unique Index Zugriff mit 2 consistent gets (+ 2 Latches; nur 2 - also 1 pro LIO -, da es sich um consistent get - examinations)
    3. non-unique Index Zugriff mit 3 consistent gets (+ 6 Latches)
    4. FTS mit 4 consistent get (+ 8 Latches)
    Warum das so ist, hat der Herr Foote übrigens gelegentlich schon mal in seinem Blog erläutert:
    • beim non-unique Index sind zwei Fetches erforderlich: einer zum Lesen des Ergebnisses und einer, um sicher zu gehen, dass nicht noch ein Satz für den gegebenen Wert folgt
    • beim unique Index kann sich Oracle die zweite Fetch-Operation sparen, da nicht mehr als ein Satz zum Schlüssel vorliegen kann 
    • auch zum FTS findet sich dort ein Artikel - und wahrscheinlich wären die beiden übrigen Artikel der fraglichen Serie Indexes And Small Tables auch noch verlinkungswürdig - falls das ein Wort sein sollte...

    Nachtrag 07.09.2011: Inzwischen hat Richard Foote die Antwort auf seine Fragen gegeben und dabei auch die angesprochenen älteren Artikel verlinkt. Interessant sind auch noch die dort angegebenen Latch-Zahlen, die ich oben im Ergebnis ergänzt habe.

    Sonntag, September 04, 2011

    used_urec

    Jonathan Lewis erläutert in seinem Blog, wie die Spalte used_urec in V$TRANSACTION gedeutet werden kann, und dass der Zusammenhang zwischen der used_unrec-Angabe und der Anzahl geänderter Zeilen eher arbiträr ist und von diversen Faktoren beeinflusst wird:
    when you insert a row, you typically generate one undo record (used_urec) for the row, but when you insert many rows using an array insert Oracle has an optimisation mechanism on undo record creation that allows it to create one used_urec to cover all the changes you have made simultaneously to an individual block - so used_urec could be much smaller than the number of rows processed.
    However, if you have indexes in place and are doing normal index maintenance on import, then each table row would require each index to be updated, so you would go back to one used_urec per table row plus one used_urec per index maintained per table row.
    So, when you look at the “big picture” there’s no obvious correlation between rows inserted and undo generated — until you look at the fine detail of exactly what you’re doing, and whether any optimisations apply. (The details of the optimisation strategies available vary with the chosen insert mechanisms and with version of Oracle).
    Außerdem verweist er auf einen älteren Artikel zum Thema Session Undo und darin dann wiederum auf ein Script von Steve Adams, mit dessen Hilfe sich die Restlaufzeit eines Rollback-Vorgangs bestimmen lässt (sofern man Zugriff auf die erforderlichen v$- und x$-Objekte besitzt).

    Automatic Diagnostic Repository Command Interpreter (ADRCI)

    Trotz des anspruchsvoll klingenden Titels nur ein Link auf Uwe Hesses einführenden Artikel zu diesem 11er Tool zur Administration von Trace-Informationen.

    Freitag, September 02, 2011

    Frequency-Histogramme und die 254 Bucket-Grenze

    Und gleich die nächste interessante Quizfrage im Blog des Herrn Foote. Bei der letzten Frage war mir die Antwort schon vor der Auflösung klar, aber diesmal hatte ich absolut keine Erklärung für das beschriebene Verhalten: für eine Spalte existierte ein Frequency-Histogramm so lange darin 254 unterschiedliche - und gleichverteilte - Werte vorlagen. Das Hinzufügen eines einzelnen Satzes mit einem neuen und weit außerhalb des bisherigen Ranges liegenden Wert (also Nr. 255) führte dann dazu, dass bei erneuter Statsitikerzeugung mit der METHOD_OPT SIZE AUTO Option kein Histogram mehr erzeugt wurde - obwohl ein solches mit der neuen Datenverteilung sehr viel sinnvoller gewesen wäre als für den Fall vor der Ergänzung (für den ohnehin eine Gleichverteilung galt, die der cbo in Abwesenheit von Histogrammen von vornherein angenommen hätte). Die Erklärung des Verhaltens lautet:
    When using METHOD_OPT SIZE AUTO, every column with 254 or less distinct values that has been referenced within a predicate, will have a Frequency-based histogram. Each and every one of them, regardless of whether the data is actually skewed or not.
    [...]
    If a column has more than 254 distinct values, whether it then has a Height-Based histogram depends on how the data is skewed.
    Für die Spalte mit dem neuen und von den bisherigen Werten massiv abweichenden Wert gilt:
    Having inserted the outlier value, it now has 255 distinct values and so no longer qualifies for an automatic frequency based histogram. However, if all its values are evenly distributed, then it won’t qualify for a height based histogram either and Column 3 only has just the one outlier value, all other values are evenly distributed values. Unfortunately, Oracle doesn’t pick up on rare outlier values (even if you collect 100% statistics and it’s one of the low/high points of the column) and so will not generate a height-based histogram.
    Der Artikel führt auch noch vor, dass das Fehlen des Histogramms dann recht extreme Fehleinschätzungen des cbo nach sich zieht, da dieser nun wieder eine Gleichverteilung annimmt. Vorgestern Abend bin ich an dieser Quizfrage fast 1,5 h hängengeblieben - es ist einfach großartig, was man bei den kompetentesten Oracle-Bloggern alles lernen kann.

    Nachtrag 03.09.2011: Randolf Geist hat in einem Kommentar zu Richard Footes Artikel noch einige wichtige Details zur Kostenberechnung des CBO mit Frequency Histograms ausgeführt.