Donnerstag, November 27, 2014

Privilege Analysis mit 12c

Eine kleine persönliche Beichte, die regelmäßige Leser vielleicht nicht allzu sehr überrascht: Performance-Fragen interessieren mich deutlich mehr als solche der Security. Und noch ein Geständnis: ich habe gelegentlich Applikationen ausgeliefert, deren Berechtigungen größer waren als erforderlich - weil es mir zu mühsam und zu öde war, mich bis ins Detail mit der Privilegienvergabe zu beschäftigen. Für Entwickler, die unter ähnlichen Charakterschwächen leiden, gibt es in 12c jetzt ein großartiges neues Feature: die Privilege Analysis, bei der man einem User initial DBA-Rechte gibt, dann die Applikation laufen lässt und anschließend feststellen kann, welche Rechte tatsächlich benötigt werden. Ein kompaktes Beispiel für die Verwendung findet man bei Franck Pachot,

Mittwoch, November 26, 2014

Instanz-Crash bei Latch-Analyse

Stefan Koehler analysiert bei SAP on Oracle eine Situation, in der das manuelle Setzen und Freigeben von Latches über Oradebug zu einem Instanz-Crash führt. Relevanter als das Ergebnis ist für mich die methodische Vorgehensweise bei der Analyse - sollte ich mich mal wieder mit Latches beschäftigen wollen, dann wäre es nützlich, diesen Artikel noch mal intensiver anzuschauen.

Montag, November 24, 2014

Zone Maps in 12c

Nur eine knappe Sammlung von Links zum Thema: Zone Maps sind eine neue Errungenschaft in 12c und stellen eine Index-artige Struktur dar, die es erlaubt, eine Filterung von Blöcken anhand der Definition von Wertebereichen durchzuführen. Dazu sind mir zuletzt mehrere Artikel von Richard Foote und ein Beitrag im Data Warehouse Insider Blog begegnet:
  • 12.1.0.2 Introduction to Zone Maps Part I (Map Of The Problematique): Richard Footes grundsätzliche Beschreibung des Features mit dem einleitenden Hinweis, dass die ZoneMaps eine Lizensierung der Partition Option und von Exadata bzw. SuperCluster erfordert - und unter diesen Umständen vermutlich erst einmal keine allzu breite Nutzung erfahren wird. Anhand eines Beispiels wird das grundsätzliche Verhalten erläutert: ZoneMaps speichern die Minimal- und Maximalwerte einer Spalte (oder Spaltenkombination) innerhalb einer Zone - also eines Bereichs von Blocks (im Umfang von 8 mb). Ausgehend von dieser Information kann der Optimizer ein Pruning irrelevanter Zonen durchführen. Dabei stellen die ZoneMaps selbst ein sehr kleines Segment dar - was angesichts der Definition nicht überrascht.
  • 12.1.0.2 Introduction to Zone Maps Part II (Changes): erklärt das Verhalten der ZoneMaps bei DML-Operationen auf der zugehörigen Tabelle. Der große Unterschied zu Indizes ist dabei, dass ZoneMaps nicht automatisch aktualisiert werden, sofern nicht ein REFRESH ON COMMIT definiert ist - in dieser Hinsicht entspricht ihr Verhalten also dem von Materialized Views. Nach DML-Operationen werden betroffene ZoneMaps als stale markiert, aber nicht betroffene Elemente können weiterhin gefiltert werden. Besonders effektiv sind ZoneMaps erwartungsgemäß bei stark geclusterten Daten.
  • 12.1.0.2 Introduction to Zone Maps Part III (Little By Little): untersucht die Auswirkungen der Daten-Clusterung genauer und anhand von Beispielen - aber am Ergebnis ändert sich nichts.
  • Optimizing Table Scans with Zone Maps: ein Überblick zum Thema vom Oracle Product Management Team, der ähnliche Aussagen enthält wie die Artikel des Herrn Foote (mit weniger Tests, dafür aber mehr Visualisierung).
Insgesamt scheint das Feature durchaus interessant und ähnlichen Konzepten verwandt zu sein, die mir anderswo begegnet sind - etwa den Data-Ids im SSAS-Kontext oder den neuen BRIN Indizes (Block Range Index) in postgres 9.5. Aber die Lizenzierungsvorraussetzungen werden es wohl zumindest vorläufig zu einer exotischen Randerscheinung machen.

Baselines und Parsing

Jonathan Lewis weist darauf hin, dass Baselines keine große Hilfe sind, wenn es darum geht, den Aufwand beim Parsen komplexer Join-Operationen zu verringern. In seinem Beispiel führt ein Join von acht Tabellen zur Prüfung von 5040 Join-Reihenfolgen - wobei die Einstellung von _optimizer_max_permutations=2000 offenbar ignoriert wird - und das ändert sich auch dann nicht, wenn eine SQL Plan Baseline für die Query erzeugt wird, weil der Optimizer zunächst versucht, einen besseren Plan zu erzeugen, ehe er die hinterlegte Baseline berücksichtigt. In entsprechenden Fällen kann es sinnvoll sein, die Hints der Baseline in ein SQL Profile zu übertragen (bzw. die Hints aus dem library cache zu extrahieren, wozu es ein Skript von Kerry Osborne und Randolf Geist gibt, das im Artikel verlinkt ist) - denn die Hints des Profiles reduzieren den Suchraum des Optimizers unmittelbar. Abschließend weist der Autor allerdings darauf hin, dass dieser Trick der Ersetzung der Baseline durch ein Profile nicht in jedem Fall funktioniert, so dass hier im Einzelfall genauere Analyse erforderlich wäre.

Donnerstag, November 20, 2014

ANSI Join Syntax und Prädikatanordnung

Die ANSI-Syntax zur Formulierung von Joins finde ich grundsätzlich deutlich lesbarer als Oracles traditionelle Schreibweise und würde sie vermutlich regelmäßig einsetzen, wenn sie nicht so viele Bugs hervorrufen würde. Einer davon ist gestern in einem OTN-Thread aufgetaucht. Dort hatte sich der Fragesteller darüber gewundert, dass zwei Varianten eines Outer-Joins unterschiedliche Ergebnisse lieferten, je nachdem, ob die ON-Prädikate direkt bei den zugehörigen FROM-Klauseln erschienen, oder getrennt von diesen in einem gemeinsamen abschließenden Block. Mein erster Gedanke war, dass die zweite Variante syntaktisch falsch sein sollte, und mein erstes Beispiel schien diese Annahme zu bestätigen, aber offenbar hängen der Erfolg oder Misserfolg - und auch das Ergebnis - von der Reihenfolge der Bedingungen ab. Dazu ein Beispiel:

-- 12.1.0.2
drop table t1;
drop table t2;
drop table t3;
 
create table t1
as
select rownum id
     , 'AAA' col1
  from dual
connect by level <= 15;
 
create table t2
as
select rownum id
     , 'BBB' col1
  from dual
connect by level <= 15;
 
create table t3
as
select rownum id
     , 'CCC' col1
  from dual
connect by level <= 10;

select /* normal ANSI syntax */
        count(*)
  from t1
  left outer join t3
    on (t3.id = t1.id)
 inner join t2
    on (t3.id = t2.id);
 
  COUNT(*)
----------
        10
 
select /* strange ANSI syntax: predicate order 1, 2 */
        count(*)
  from t1
  left outer join t3
 inner join t2
    on (t3.id = t1.id)
    on (t3.id = t2.id);

    on (t3.id = t1.id)
                *
FEHLER in Zeile 6:
ORA-00904: "T1"."ID": ungültiger Bezeichner
 
select /* strange ANSI syntax: predicate order 2, 1 */
        count(*)
  from t1
  left outer join t3
 inner join t2
    on (t3.id = t2.id)
    on (t3.id = t1.id);
 
  COUNT(*)
----------
        15

Demnach ergibt sich:
  • die (mehr oder minder) normale Anordnung der Elemente liefert ein Ergebnis mit 10 rows.
  • die erste Version mit Trennung der FROM-Klauseln von den ON-Prädikaten und einer Anordnung der Prädikate (t3-t1, t3-t2) liefert einen Fehler.
  • die zweite Version mit Trennung der FROM-Klauseln von den ON-Prädikaten und einer Anordnung der Prädikate (t3-t2, t3-t1) liefert 15 rows.
Wenn man dazu ein CBO-Trace erzeugt (Event 10053) wird deutlich, dass der Optimizer im Rahmen seiner Transformationen den Outer Join aus der ersten Query ausschließt, während er in der zweiten (lauffähigen) Query erhalten bleibt, woraus sich die 10 bzw. 15 Ergebnissätze ergeben:

-- Final query after transformations (reformatted)
/* normal ANSI syntax */
SELECT COUNT(*) "COUNT(*)"
   FROM "C##TEST"."T1" "T1"
      , "C##TEST"."T3" "T3"
      , "C##TEST"."T2" "T2"
  WHERE "T3"."ID" = "T2"."ID"
    AND "T3"."ID" = "T1"."ID"

/* strange ANSI syntax: predicate order 2, 1 */
SELECT COUNT(*) "COUNT(*)"
  FROM "C##TEST"."T1" "T1"
     , (SELECT "T3"."ID" "ID"
          FROM "C##TEST"."T3" "T3"
             , "C##TEST"."T2" "T2"
         WHERE "T3"."ID" = "T2"."ID") "from$_subquery$_004"
WHERE "from$_subquery$_004"."ID"(+) = "T1"."ID"

Ich muss gestehen, dass ich mir nicht völlig sicher bin, welche der beiden Interpretationen der Join-Bedingungen korrekt ist - grundsätzlich handelt es sich offenbar um eine Frage der Reihenfolgen:
  • wenn zunächst der Outer Join von t1 und t3 erfolgt, der 15 rows liefert, und dann ein Inner Join dieses Ergebnisses an t2 durchgeführt wird und die Verknüpfung über die t3.id erfolgt, die im ersten Zwischenergebnis NULL ist, dann ist das Ergebnis 10 korrekt.
  • wenn zunächst der Inner Join von t2 und t3 erfolgt und dann der Outer Join dieses Zwischenergebnisses an t1, dann sollte sich die 15 ergeben.
Persönlich würde ich in einem solchen Fall wahrscheinlich eine explizite Klammerung über Inline-Views (oder CTEs) durchführen (und hoffen, dass der Optimizer diese Klammerung nicht wieder durch Transformationen beseitigt). Ein Bug scheint jedenfalls zu sein, dass die Reihenfolge der Prädikate darüber entscheidet, ob ein Fehler oder ein Ergebnis zurückgeliefert wird. Ich vermute, dass tatsächlich die Reihenfolge der Prädikate entscheidet, welche Verknüpfung Vorrang besitzt - und möglicherweise sagt die ANSI-Spezifikation mehr darüber aus, wie die Verarbeitungsreihenfolge der Join Operationen gedacht ist - wenn ich dazu noch Hinweise finde, ergänze ich sie. Ein Indiz dafür, dass tatsächlich der Inner Join Vorrang haben sollte, liefert postgres: dort liefern beide Varianten 15 Datensätze.

Korrektur: die abschließende Aussage zu postgres war falsch (basierend auf einem copy&paste-Fehler) - tatsächlich verhält sich postgres exakt genauso wie Oracle und liefert 10 rows, einen Fehler ("ERROR:  invalid reference to FROM-clause entry for table t1. TIP:  There is an entry for table t1, but it cannot be referenced from this part of the query.") bzw. 15 rows.

Nachtrag 21.11.2014: Patrick Barel hat mich in seinem Kommentar davon überzeugt, dass es sich nicht um einen Bug handelt (worauf bereits das postgres-Verhalten hindeutete), sondern um ein definiertes Verhalten. Seltsam sieht es trotzdem aus...

Mittwoch, November 19, 2014

SQL Plan Directive in 12c

Mir ist gerade aufgefallen, dass ich bisher nichts über die in 12c eingeführten SQL Plan Directives geschrieben habe, die mir erstmals im White Paper Optimizer with Oracle Database 12c von Maria Colgan begegnet sind. In der Dokumentation wird die SQL Plan Directive folgendermaßen definiert:
A SQL plan directive is additional information and instructions that the optimizer can use to generate a more optimal plan. For example, a SQL plan directive can instruct the optimizer to record a missing extension.
Grundlage für die Anlage einer Direktive sind die Informationen, die über die Automatic Reoptimization Features ermittelt werden. Wenn das Cardinality Feedback (das sich in 12c Statistics Feedback nennt) für eine rowsource Operation eine Abweichung zwischen erwarteter und tatsächlicher Satzanzahl mitteilt, dann wird eine SQL Plan Directive erzeugt, die dem Optimizer mitteilt, bei der nächsten Optimierung der Query zusätzliche dynamic sampling Informationen für Tabellen oder Spaltengruppen zu erfassen - wobei die extended statistics für die column group im Erfolgsfall auch persistent angelegt werden, was die Verwendung der Direktive dann überflüssig machen kann. Die Inhalte der Direktiven sind in den Dictionary Views DBA_SQL_PLAN_DIRECTIVES und DBA_SQL_PLAN_DIR_OBJECTS dargestellt. Direktiven sind nicht an eine SQL_ID gebunden, sondern können für ähnliche Queries herangezogen werden (etwa, wenn die Unterschiede auf der Ebene unterschiedlicher Spalten in der Select-Liste liegen).

Einige interessante Hinweise zu den Änderungen, die sich für die Direktiven in 12.1.0.2 ergeben haben, findet man bei Franck Pachot. Insbesondere erläutert er die Status-Angaben der STATE-Spalte in DBA_SQL_PLAN_DIRECTIVES.

In 12.1.0.1 gab es folgende Status-Angaben:
  • NEW: der Status nach der Anlage der Direktive: zu diesem Zeitpunkt ist nur bekannt, dass es eine Abweichung zwischen erwarteten und tatsächlichen Cardinalities gab.
  • MISSING_STATS: ergibt sich, wenn nach einer folgenden Optimierung (der gleichen oder einer ähnlichen Query) fehlende Statistiken als Ursache der Abweichung bestimmt wurden.
  • HAS_STATS: gibt an, dass die zusätzlichen Statistiken genügen, um die Ausführung zu optimieren.
  • PERMANENT: gibt an, dass die zusätzlichen Statistiken die Fehler in den Schätzungen nicht vermeiden - und deshalb ignoriert werden können.
Für 12.1.0.2 gibt es nur noch zwei Status-Angaben:
  • USABLE: fasst die bisher unter NEW, MISSING_STATS und PERMANENT aufgeschlüsselten Angaben zusammen. Die Semantik ist etwa: die Direktive muss vom Optimizer untersucht werden, aber das Problem ist noch nicht behoben.
  • SUPERSEDED: gibt an, dass die erforderlichen Statistiken erzeugt wurden (HAS_STATS) oder dass die Direktive durch eine andere Direktive überflüssig gemacht wurde.
Demnach ist die Detaillierung der Angaben in 12.1.0.2 geringer, aber die Status-Angaben der älteren Version sind in der (XML-) Spalte NOTES weiterhin verfügbar.

Mein Eindruck ist dabei, dass es sich um ein interessantes Feature mit großem Entwicklungspotential handelt - aber auch um eines, dessen Dokumentation noch eine ganze Menge offener Fragen lässt.

Freitag, November 14, 2014

Materialized Views für postgres

Mit Materialized Views hat sich mein erster Blog-Eintrag zu postgres im Jahr 2005 beschäftigt. Erst seit Version 9.3 gibt es reguläre MViews in postgres, aber Craig Kerstiens urteilt knapp: 
The problem with them is they were largely unusable. This was because they 1. Didn’t auto-refresh and 2. When you did refresh them it would lock the table while it ran the refresh making it unreadable during that time.
In 9.4 ist zumindest das zweite Problem behandelt: mit Hilfe des Befehls REFRESH MATERIALIZED VIEW CONCURRENTLY kann man den Neuaufbau der MView durchführen, ohne massive Locks zu halten - allerdings muss dazu ein unique index existieren, der sämtliche Datensätze enthält.

Dienstag, November 11, 2014

Import Foreign Schema für Postgres FDW

Vor ziemlich genau einem Jahr habe ich an dieser Stelle über database links und foreign data wrapper (FDW) geschrieben und mich darüber beklagt, wie unhandlich sich die Einbindung der Objekte unterschiedlicher Datenbanken eines Clusters in einer gemeinsamen Analyse-Datenbank gestaltet, weil man für jede Tabelle eine explizite Definition der Spalten angeben muss, was sich nicht ohne größeren Aufwand automatisieren lässt. In Version 9.5 soll es dafür nun eine Abhilfe geben: Michael Paquier beschreibt das FDW-Kommando IMPORT FOREIGN SCHEMA, das die Einbindung eines kompletten Schemas einer remote-Datenbank ermöglicht: "This command provides an automated way to create foreign table definitions that match remote tables, thereby reducing tedium and chances for error." Mein herzlicher Dank für diese Ergänzung an die postgres-Entwicklung.

Montag, November 10, 2014

SQL Profiles, Hints und Optimizer-Transformationen

Die Möglichkeiten des Plan-Managements sind in aktuellen Oracle-Releases bekanntlich recht umfangreich: neben den historischen Stored Outlines gibt es SQL Plan Baselines, SQL Profiles und SQL Patches. Jede dieser Techniken hat ihre Anwendungsbereiche - und ihre Beschränkungen. Franck Pachot beschäftigt sich in seinem jüngsten Artikel mit SQL Profiles und untersucht die Frage, wieso ein Profile für eine einzelne Tabelle mehrere OPT_ESTIMATE Hints enthalten kann (die die Cardinality-Schätzungen des Optimizers modifizieren). Zur Erinnerung noch mal eine minimale Definition für SQL Profiles: sie sind eine Sammlung von ergänzenden Hints, die der Optimizer für eine Query anlegt, wenn man ihm die Zeit lässt, seine Arbeit in Ruhe durchzuführen - statt unter dem üblichen OLTP-Zeitdruck; für Data Warehouse Systeme (oder wie man die Nicht-OLTP-Systeme sonst einordnen mag) kann das eine ziemlich gut Idee sein, wenn das Parsen gegenüber der Ausführung einer Query kaum ins Gewicht fällt. Aber zurück zum Artikel: durch einen Blick ins CBO-Trace wird klar, dass das mehrfache Erscheinen der Hints den unterschiedlichen Transformationen geschuldet ist, die der Optimizer im Rahmen seiner Optimierungsversuche durchspielt. Ausgehend von dieser Beobachtung kommt der Herr Pachot zu folgendem Schluss:
That observation brings me to the following: what happens to your profiles when you upgrade to a version that brings new optimizer transformations? The optimizer will compare some plans with adjusted cardinalities, compared with some plans with non-adjusted cardinalites. And that is probably not a good idea.
In my opinion, SQL Profiles are just like hints: a short term workaround that must be documented and re-evaluated at each upgrade.
Und das würde ich grundsätzlich unterschreiben - wobei ich mir vorstellen könnte, dass es Umgebungen gibt, in denen es sinnvoll wäre, wenn man dem Optimizer grundsätzlich mehr Zeit zum Nachdenken gibt; aber das wäre wieder ein anderes Feature.

Freitag, November 07, 2014

In-Memory Queries und Joins

Maria Colgan setzt ihre einführende Serie zum Verhalten der In-Memory Features in 12.1.0.2 mit einem Artikel Getting started with Oracle Database In-Memory Part IV - Joins In The IM Column Store fort, in dem sie - man ahnt es vielleicht schon - das Verhalten von Joins zwischen In-Memory-Objekten erläutert. Im Beispiel wird ein Join zwischen einer Faktentabelle und einer Dimensionstabelle durchgespielt, der angesichts fehlender Indizes sinnvollerweise als Hash Join durchgeführt wird. Zur Optimierung dieser Operation wird beim Aufbau der Hash Tabelle auf der kleineren Datenmenge zusätzlich ein Bloom Filter (also ein Bit Vektor) angelegt, der als zusätzliches Prädikat an den Scan der zweiten Tabelle übergeben wird. Nach Anwendung der Prädikate der Where-Bedingung wird der Bloom-Filter überprüft und nur die ihm entsprechenden Sätze werden dann an die Hash-Prüfung im Join weitergegeben (dazu gibt's eine übersichtliche Grafik, der leichter zu folgen sein dürfte als den beiden vorangehenden Sätzen). Im Ausführungsplan erscheint der Filter - wie üblich - mit einem Namen :BF0000. Das Verfahren funktioniert auch bei paralleler Ausführung und kann die üblichen Optimierungen verwenden, die dem Optimizer bei Operationen zur Verfügung stehen, bei denen keine In-Memory-Objekte beteiligt sind - also z.B. die Veränderung der Join-Reihenfolge im Hash Join über die swap_join_inputs-Optimierung. Auch diese Erklärung deutet darauf hin, dass es den Oracle-Entwicklern tatsächlich gelungen ist, die In-Memory-Objekte tief in die Kern-Elemente des Systems einzubetten und so auf abenteuerliche Sonderfallbehandlungen verzichten zu können.

Mittwoch, November 05, 2014

Probleme mit Cardinality-Feedback

Dieser Tage hat mir mein Bruder mitgeteilt, dass ich die Wendung "Dieser Tage" zu Einleitungszwecken etwas zu sehr strapaziere.

Dann vielleicht: gestern? Gestern also hat Jonathan Lewis in seinem Blog einen Artikel zum Cardinality-Feedback veröffentlicht, der auf einer Frage basiert, die vor einigen Tagen in Oracle-L aufgeworfen wurde - und bei der es darum ging, dass eine Query, die bei der ersten Ausführung eine angemessene Laufzeit aufwies, bei der zweiten Ausführung sehr langsam wurde, was sich auf die Wirkungen des Cardinality-Feedbacks zurückführen ließ. Nun gibt es eine ganze Reihe bekannter Bugs im Zusammenhang mit dem Cardinality-Feedback, aber im Artikel geht es nicht um einen Bug, sondern um eine grundsätzliche Beschränkung des Features.

Zur Klärung der Voraussetzungen noch mal die einführende Erklärung zum Thema aus dem Blog der CBO-Entwicklung (von Allison Lee, nehme ich an):
Cardinality feedback was introduced in Oracle Database 11gR2. The purpose of this feature is to automatically improve plans for queries that are executed repeatedly, for which the optimizer does not estimate cardinalities in the plan properly.
Das klingt zunächst wie eine gute Idee, aber der Herr Lewis konstruiert im Artikel ein relativ einfaches Beispiel dafür, wie der Optimierungsversuch ein unglückliches Ende finden kann - dann nämlich, wenn mehrere Tabellen im Spiel sind, zu denen die Schätzungen des CBOs nicht viel taugen: durch das Cardinality-Feedback wird im Beispiel der initiale Plan verworfen, weil die tatsächliche Satzanzahl für die erste Tabelle im Join um den Faktor 20 höher war als erwartet (2000 statt 100 Iterationen im NL-Join). Leider verschätzt sich der Optimizer bei der Erzeugung des neuen Plans bei der zweiten Tabelle aber um den Faktor 100 (20000 statt 200), so dass der neue Plan noch sehr viel ungünstiger ausfällt als die ursprüngliche Version. Und da Cardinality-Feedback nur einmal berücksichtigt wird, bleibt es dann bei der schlechteren Lösung. Mit einem Cardinality-Hint könnte man in 11.2 in einem solchen Fall gegensteuern und den geeigneteren Hash Join hervorrufen. In 12.1 wird für den Beispiel-Fall ein adaptive Plan erstellt und bei wiederholter Ausführung erhält man dann den gewünschten Hash Join - aber grundsätzlich gibt es das Cardinality-Feedback auch in 12.1 (unter dem Namen "Statistics Feedback"); obwohl es unter Oracles Optimierungsstrategien nicht unbedingt die beliebteste ist...

Sonntag, November 02, 2014

Twitter

Wahrscheinlich ist es den aufmerksameren Lesern bereits aufgefallen, dass ich vor kurzem damit begonnen habe, meine Twitter-Botschaften in diesem Blog einzubinden. Der Hintergrund dafür ist, dass ich nach jahrelangem Grübeln endlich dahinter gekommen bin, was ich auf 140 Zeichen unterbringen kann - nämlich minimal kommentierte Links. Früher habe ich aus so was gelegentlich eine zweizeilige Blog-Notiz gemacht, aber da ich solche Verweise ohnehin gleich wieder vergesse, wenn ich mir nicht ein paar Details dazu notiere, kann ich sie auch gleich in anderes Medium auslagern, wo ich sie genauso leicht wiederfinde - so gut wie die blogspot-Suche ist die Twitter-Suche vermutlich auch...

Bitmap-Index zur Zeilenzählung

Auf die folgende Idee hat mich Richard Foote mit dem jüngsten Artikel seiner Serie zur Index Advanced Compression gebracht, in dem er erläutert, dass Bitmap-Indizes aufgrund ihrer extremen Komprimierbarkeit weiterhin ihre Bedeutung behalten (ein paar Anmerkungen zur Serie habe ich hier notiert). In einer Tabelle, bei der die exakte Kenntnis der Satzanzahl von extremer Bedeutung ist, könnte man diese Information durch Ergänzung eines Bitmap-Index auf einer Spalte mit konstantem Wert sehr effektiv ermitteln. Dazu ein kleines Beispiel:

drop table t;
 
create table t (
    id not null
  , padding
  , count_helper
)  
as
select rownum id
     , lpad('*', 50, '*') padding
     , cast(null as number) count_helper
  from dual
connect by level <= 100000;

create bitmap index t_bix on t(count_helper);
 
create unique index t_ix on t(id);
 
set autot trace
 
select count(*) from t;

-------------------------------------------------------------------------------
| Id  | Operation                     | Name  | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |       |     1 |     3   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE               |       |     1 |            |          |
|   2 |   BITMAP CONVERSION COUNT     |       |   100K|     3   (0)| 00:00:01 |
|   3 |    BITMAP INDEX FAST FULL SCAN| T_BIX |       |            |          |
-------------------------------------------------------------------------------

Statistiken
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          7  consistent gets
          3  physical reads
          0  redo size
        365  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

alter index t_bix invisible;

select count(*) from t;

----------------------------------------------------------------------
| Id  | Operation             | Name | Rows  | Cost (%CPU)| Time     |
----------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |     1 |    30  (10)| 00:00:01 |
|   1 |  SORT AGGREGATE       |      |     1 |            |          |
|   2 |   INDEX FAST FULL SCAN| T_IX |   100K|    30  (10)| 00:00:01 |
----------------------------------------------------------------------

Statistiken
----------------------------------------------------------
         29  recursive calls
          0  db block gets
        263  consistent gets
        208  physical reads
          0  redo size
        365  bytes sent via SQL*Net to client
        499  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          6  sorts (memory)
          0  sorts (disk)
          1  rows processed

Der Bitmap-Index auf der Spalte count_helper, die nur NULL-Werte enthält (was den Bitmap-Index - anders als einen B*Tree-Index - nicht berührt) ist winzig und erlaubt die Berechnung der Satzanzahl durch einen sehr billigen BITMAP CONVERSION COUNT: Grundlage sind die minimale und die maximale rowid und die Kenntnis der Anzahl dazwischen liegender Elemente. Wenn man den Bitmap Index als invisible markiert, wird der eindeutige B*Tree-Index auf der (NOT NULL-) Spalte id verwendet, der ebenfalls über einen FAST FULL SCAN eingelesen wird, was aber bei diesem sehr viel größeren Segment sehr viel mehr Arbeit hervorruft (263 consistent gets gegenüber 7 für den bitmap-Fall). Dass der B*Tree-Index so viel größer ist, liegt in erster Linie daran, dass er in den Leaf-Blöcken die rowids der zugehörigen Sätze explizit enthalten muss (während der bitmap-Index eine Offset-basierte Speicherung verwendet). Als NULL-Wert am Satzende bringt der count_helper auch keine deutliche Vergrößerung der Tabelle mit sich. Trotzdem ist diese Idee natürlich nur in ganz speziellen Fällen relevant - nämlich wenn die Kenntnis der genauen Satzanzahl relevant ist und wenn keine massiven konkurrierenden DML-Operationen stattfinden. Aufgrund der massiven Locking-Probleme, die bitmap-Indizes mit sich bringen, sind sie für OLTP-Systeme grundsätzlich eher ungeeignet. Und wenn nur eine ungefähre Kenntnis der Satzanzahl erforderlich ist, kann in 12.1.0.2 auch die neue APPROX_COUNT_DISTINCT verwendet werden. Eine weitere Alternative zum bitmap-Index könnte auch eine Materialized View sein, die die Aggregation in ein zusätzliches Hilfsobjekt verschiebt. Aus Gründen der Vorsicht habe ich die Idee als Kommentar in Richard Footes Blog untergebracht und mir von ihm noch mal bestätigen lassen, dass mir an dieser Stelle kein relevanter Punkt entgangen ist.