Sonntag, Mai 31, 2015

Space-Management und Direct Path Ladeoperationen

Im Blog der CBO-Entwicklung erklärt Nigel Bayliss detailliert, wie das Management des Speicherplatzes in den Datenblocks bei direct path load funktioniert. Tatsächlich gibt es in diesem Zusammenhang mehrere unterschiedliche Strategien und erst in 12.1.0.2 wurde die Information, welche Strategie tatsächlich verwendet wird, in den Ausführungsplänen ergänzt. Der Grund für die Verwendung mehrerer Strategien ist dabei, dass das Verfahren in sehr unterschiedlichen Kontexten einsetzbar sein muss: für partitionierte und nicht partitionierte Tabellen, für serielle und parallele Operationen, in Einzel-Datenbank-Instanzen und für RAC-Systeme. Die verwendeten Strategien sind dabei folgende:

High Water Mark (HWM) Loading

Dies ist wohl das einfachste und am leichtesten zu verstehende Verfahren: neue Daten werden in Extents oberhalb der HWM eingefügt. Verwendet werden kann es für serielle Ladeoperationen, parallele Ladeoperationen, die über den partition key (PKEY) verteilt werden, und equi-partition loads (die zum Beispiel dann verwendet werden, wenn Daten zwischen zwei equi-partitioned tables kopiert werden, sofern die Anzahl der Partitionen relativ groß ist).

Temp Segment Merge (TSM) Loading

Der älteste Mechanismus, der für parallele Loads entwickelt wurde, und das Standardverfahren für parallele loads in ein einzelnes Segment in 11.2 (sofern kein Auto-DOP im Spiel ist). Dabei wird jeder parallel execution server producer mit einem temporären Segment verbunden, in das er seine Ergebnisse schreibt, und bei Commit erfolgt ein Merge dieser temporären Segmente. Das Verfahren skaliert recht gut, weil die einzelnen Operationen klar voneinander getrennt sind: eine Erhöhung des DOP verbessert folglich die Performance. Allerdings hängt der DOP mit der minimalen Anzahl ergänzter Extents zusammen: DOP 16 ergänzt also mindestens 16 Extents - was unter anderem auch zu (ich nenn es so, obwohl ich weiß, dass ich dafür im OTN-Forum keine Pluspunkte bekäme) Fragmentierungseffekten führen kann.

High Water Mark Brokering (HWMB) Loading

Dieses Verfahren kann verwendet werden, wenn mehrere PX Server (potentiell) das gleiche Segment füllen. Es wird in 11g und 12c häufig verwendet, wenn mehrere Partitionen gleichzeitig befüllt werden, und ist in 11g das gewählte Verfahren für single-segment loads mit Auto-DOP. Bei diesem Verfahren ist eine Koordinierung der Verschiebung der HWM erforderlich und dafür sorgt eine HV enqueue, die die HWM-Verschiebungen der PX Server Prozesse abstimmt. Bei diesem Verfahren entstehen in der Regel weniger neue Extents als beim TSM-Verfahren.

Hybrid TSM/HWMB Loading in 12c

Wurde insbesondere zur Optimierung von Einzel-Segment loads eingeführt. Im Execution Plan erscheint dafür ein step "LOAD AS SELECT (HYBRID TSM/HWMB)". Das Verfahren kombiniert - wie der Name schon vermuten lässt - die positiven Eigenschaften von TSM und HWMB, insbesondere in RAC-Systemen. Dabei treten wiederum die HV enqueues zur Koordinierung der Verschiebung der HWM in einem Extent auf, aber zusätzlich werden zur gleichen Zeit mehrere temporäre Segmente eingesetzt. Das Verfahren ist besser skalierbar als die beiden Alternativen (vor allem in RAC-Systemen und bei hohem DOP) und reduziert die Anzahl erzeugter Extents. In 12c wird Hybrid TSM/HWMB für INSERT und MERGE in ein Einzel-Segment verwendet (unabhängig von der Verwendung von manuellem oder automatischem DOP). TSM wird weiterhin für bestimmte parallelisierte Muli-Segment-Ladeoperationen verwendet. Vor allem aber erscheint das verwendete Verfahren explizit in den Ausführungsplänen, wo es jeweils beim LOAD AS SELECT erscheint.

Ein hochinteressanter Artikel, denn so detailliert erläutert das CBO-Team die internen Mechanismen selten. Insbesondere die Aufnahme der Information in die Ausführungspläne finde ich ausgesprochen nützlich. Gerne hätte ich die Aussagen ins Deutsche übertragen, aber dem Leser wird aufgefallen sein, dass ich diesmal noch früher aufgegeben habe als üblich und irgendwo zwischen Englisch und Deutsch hängen geblieben bin.

Freitag, Mai 29, 2015

Big Table Caching mit 12c

Eigentlich glaubte ich, mich erinnern zu können, hier schon mal einen Eintrag zum in 12c eingeführten Big Table Caching verlinkt zu haben - aber offenbar ist das nicht der Fall. Daher nun der Verweis auf die einleitende Untersuchung zum Thema von Martin Bach. Grundsätzlich dient das Feature dazu, einen Anteil des Buffer Caches für Full Scans zu reservieren. Dieser Anteil kann zwischen 0% (default) und 90% liegen, mindestens 10% müssen für sonstige Operationen verfügbar bleiben. Zur Analyse des Zustands des Big Table Caches dienen die Views v$bt_scan_cache (mit den Basisinformationen zum Cache) und v$bt_scan_obj_temps (mit Informationen zum caching einzelner Objekte). Darüber hinaus gibt es eine Reihe von Session-Statistiken, deren Namen dem Muster 'STAT, data warehousing%' entsprechen - was auch noch einmal darauf hinweist, in welchem Kontext das Feature anzusiedeln ist.

Dienstag, Mai 26, 2015

Franck Pachot über SQL Plan Directives

In den letzten Wochen hat Franck Pachot eine Reihe interessanter Artikel zum Verhalten von SQL Plan Directives veröffentlicht, die ich an dieser Stelle grob vereinfachend zusammenfasse. Eine Einleitung zum Thema wollte ich mir eigentlich sparen, da ich etwas Derartiges hier bereits untergebracht hatte, aber der Herr Pachot hat es in Matching SQL Plan Directives and extended stats so schön zusammengefasst, dass ich mir seine Erklärung ausborge:
SQL Plan Directives in USABLE/MISSING_STATS state can create column groups and extended stats on it at the next dbms_stats gathering. When the next usage of the SPD validates that static statistics are sufficient to get good cardinality estimates, then the SPD goes into the SUPERSEDED/HAS_STATS state. If an execution still see misestimates on them, then the state will go to SUPERSEDED/PERMANENT and dynamic sampling will be used forever. Note that disabled SPD can still trigger the creation of extended statistics but not the dynamix sampling.
Die Artikel beschäftigen sich mit diversen Detailfragestellungen:
  • DBA_SQL_PLAN_DIRECTIVE.LAST_USED: weist darauf hin, dass die last_used-Angabe in der View nur eine sehr eingeschränkte Präzision besitzt und bei Verwendung um das eigenartige Inkrement 6,5 Tage erhöht wird.
  • Matching SQL Plan Directives and extended stats: leider gibt es keinen direkten Weg, um die Direktiven (aus DBA_SQL_PLAN_DIRECTIVES) mit den zugehörigen extended statistics (DBA_STATS_EXTENSION) zu verknüpfen, da sich das Format der gespeicherten Daten unterscheidet (XML bzw. CLOB). Man kann allerdings eine Query zum Parsen der Angaben formulieren und die Resultate dann joinen - und genau das hat der Herr Pachot auch getan. Außerdem formuliert er eine Reihe interessanter Fragen zum Umgang mit dem Feature, die aber keine definitive Antwort erhalten (können).
  • How to import SQL Plan Directives: zeigt, wie man Direktiven in eine Staging Tabelle packt, transportiert und andernorts wieder auspackt (wobei packen und entpacken über dbms_spd-Routinen realisiert sind). Das Verfahren ähnelt dem, das beim Transport von SQL Plan Baselines zu verwenden ist.
  • Matching SQL Plan Directives and queries using it: erläutert, dass es nicht ganz leicht ist, zu bestimmen, weil Direktive für welche Query verwendet wurde: explain plan (bzw. dbms_xplan.display) liefert diese Information, aber sie fehlt in v$sql_plan: "It's not easy to match all queries that can use a SQL Plan Directive, but you can do it on the other way: do an explain plan for each query you suspect and check the notes. If you are ready to parse a lot of queries, you can also do it automatically."
Offensichtlich beschäftigt sich Frank Pachot zur Zeit recht intensiv mit dem Thema, so dass ich vermute, dass da noch weitere Artikel zu erwarten sind, die ich gegebenenfalls zu ergänzen plane.

Donnerstag, Mai 21, 2015

Dom Brooks über Plan-Stabilität

Dom Brooks zählt zu den Blog-Autoren, die sich am intensivsten mit Fragen der Stabilität von SQL-Ausführungsplänen beschäftigt haben. In Strategies for Minimising SQL Execution Plan Instability liefert er einen recht umfassenden historischen Überblick zur Entwicklung des Optimizers und jener Ergänzungen, die eingeführt wurden, um seine Informationsgrundlagen und Entscheidungsmöglichkeiten zu erweitern. Dabei gilt natürlich, dass die größere Flexibilität und die Einführung adaptiver Strategien dazu führen, dass die Stabilität der Optimizer-Entscheidungen geringer wird. Die grundlegende Frage dabei ist: "Do you want the Optimizer to try to get a better execution plan sometimes? If the answer is yes, then you have to accept that it will get it wrong from time to time." Um die Stabilität von Plänen wieder zu erhöhen, wurden im Laufe der Zeit diverse Hilfsmittel eingeführt, vor allem SPM (SQL Plan Management) in 11g, die es erlauben, die Planauswahl des Optimizers zu kontrollieren und der Autor erläutert ausführlich, welche Möglichkeiten in diesem Zusammenhang existieren und wo ihre Einschränkungen liegen. Insgesamt liefert der Artikel eine ziemlich vollständige Liste der im Rahmen der Fragestellung der Stabilität/Instabilität von Plänen relevanten Faktoren.

Mittwoch, Mai 20, 2015

postgres Ressourcen

Mit den Links zu den - in der Regel sehr lesenswerten - Artikeln des dbi-services Blog habe ich manchmal nicht allzu viel Glück, weil sie bisweilen statt beim gewünschten Artikel auf der Latest Entry Seite ankommen (was laut Franck Pachot ein Joomla-Problem sein könnte - und demnächst erledigt sein sollte, da ein Umzug nach wordpress geplant ist). Trotzdem versuche ich es ein weiteres Mal mit einer Verlinkung: diesmal auf den Artikel "What will be coming with the next major version of postgresql?" von Daniel Westermann, der die einschlägigen Ressourcen zur postgres-Weiterentwicklung zusammenfasst, darunter vor allem auch:
Persönlich verfolge ich nur die Blogs, in denen letztlich alles interessanten Entwicklungen angesprochen werden.

Montag, Mai 18, 2015

Upsert in postgres

In meiner universitären Vergangenheit hätte ich es vielleicht "ein Desiderat der Forschung" genannt: ein Kommando, mit dessen Hilfe sich in postgres ein INSERT oder UPDATE in Abhängigkeit davon durchführen lässt, ob ein gegebener Schlüssel bereits in der Zieltabelle vorliegt. Diese in anderen Datenbanken als MERGE oder UPSERT bezeichnete Funktionalität ist seit vielen Jahren die wahrscheinlich größte Lücke im ansonsten sehr mächtigen SQL-Dialekt von postgres. Mit Release 9.5 wird diese Lücke nun offenbar endlich geschlossen - wobei sich die postgres-Entwickler dafür entschieden haben, die Option als Ergänzung des INSERT-Kommandos zu implementieren:
INSERT INTO ... VALUES ...
   ON CONFLICT
   DO UPDATE SET ...
Ein paar einführende Erläuterungen zum Verhalten liefern unter anderem Craig Kerstiens und Michael Paquier, vor allem aber Hubert Lubaczewski, der - wie üblich - die umfangreichste Untersuchung liefert und dabei auch die Performance betrachtet.

Dienstag, Mai 12, 2015

Degenerierte Indizes

Seit ich begonnen habe, mich mit Datenbanken zu beschäftigen, lautet eine der populärsten Fragen in Oracle-Foren: mit welcher Häufigkeit soll ich meine B*Tree-Indizes neu aufbauen lassen? Zu behaupten darüber hätten Kriege stattgefunden, wäre vielleicht ein wenig übertrieben - aber heftige Auseinandersetzungen waren es allemal. Inzwischen ist dieser Konflikt weitgehend zur Ruhe gekommen, was vermutlich damit zusammen hängt, dass die Antwort eigentlich unstrittig ist: Oracles Implementierung von Indizes macht Rebuilds weitgehend unnötig - sie sind nur für seltene Fälle mit besonderen Bedingungen für die zugehörigen DML-Operationen relevant. Richard Foote hat sich schon vor mehr als zehn Jahren in Oracle B-Tree Index Internals: Rebuilding The Truth mit den Aussagen der Befürworter von regelmäßigen Rebuild-Operationen auseinander gesetzt und diese widerlegt. Trotzdem findet man den Vorschlag solcher Rebuilds auch noch in jüngeren Publikationen, wobei die Formulierungen allmählich den Bereich unfreiwilliger Komik erreichen, was David Aldridge auf Richards Footes Webseite kommentierte:
Interesting quote from Page 728 of that book: “Even if index rebuilding were to be proven as [sic] a useless activity, the Placebo effect on the end users is enough to justify the task.”. It opens up a whole new class of performance tuning … perhaps it could be called “Faith-based Tuning”.
Jonathan Lewis schrieb dazu: "If the only effect is a placebo effect couldn’t you just tell the users that you had rebuilt the indexes rather than actually doing it?"

So viel zur Geschichte - wobei es sich aus meiner Sicht um Ausschnitte aus dem Kapitel zur Auseinandersetzung zwischen wissenschaftlicher und mythischer Welterklärung handelt. Aber der eigentliche Anlass für diesen Eintrag war ein anderer: Mohamed Houri hat gerade einen Artikel zu seiner Verwendung des Scripts Index Sizing von Jonathan Lewis veröffentlicht, mit dessen Hilfe er einen jener seltenen degenerierten Indizes identifizieren konnte, die tatsächlich von einem Rebuild profitieren - also deutlich komprimiert werden können. Letztlich leistet das Skript dabei nicht mehr (und nicht weniger), als die tatsächliche Größe eines Index mit der angesichts des Datenvolumens erwartbaren Minimalgröße zu vergleichen. Mohamed ergänzt dieses Ergebnis in seinem Artikel um eine Visualisierung der durch die Funktion sys_op_lbid ermittelten Anzahlen von Index-Einträgen pro Leaf-Block und diese Darstellung zeigt, dass es neben einer großen Zahl gut gefüllter Blocks (mit 422 Einträgen) eine noch größere Anzahl relativ schwach gefüllter Blöcke (mit 113 Einträgen) gibt. In seinem Kommentar zum Artikel erinnert Jonathan Lewis wiederum daran, dass hier womöglich das bekannte Problem der Vervielfältigung von ITL Einträgen im Spiel ist, das in solchen Fällen regelmäßig aufzutreten pflegt.

Vielleicht hätte ich mir (und meinen vorgestellten Lesern) den historischen Einstieg auch ersparen können, aber offenbar habe ich inzwischen das Alter erreicht, in dem man beginnt, von den alten Zeiten zu schwadronieren, in denen alles besser/schlechter/anders gewesen ist.

Freitag, Mai 08, 2015

Optimizer-Transformationen: Predicate Pushing in Oracle und Postgres

Das in der Praxis am häufigsten verwendete Verfahren der Query-Performance-Optimierung ist vermutlich in allen RDBMS immer noch das der Umformulierung: da es in SQL für so ziemlich jede Fragestellung mehrere Lösungsmöglichkeiten gibt, kann man in aller Regel versuchen, eine andere syntaktische Variante zu wählen - und hoffen, dass der Optimizer dafür einen effektiveren Plan findet. Ich will dieses Verfahren nicht grundsätzlich negativ bewerten: es kann ohne Zweifel seine Erfolge vorweisen und es gibt RDBMS, bei denen die Instrumentierung so mangelhaft ist, dass man mit ihrer Hilfe nicht allzu viele Hinweise auf die eigentlichen Probleme der Ausführung erhält. Die meisten RDBMS besitzen allerdings inzwischen eine gute oder sogar sehr gute Instrumentierung, die eine exaktere Analyse von Zugriffsproblemen gestattet.

Darüber hinaus sind die Optimizer der meisten RDBMS selbst dazu in der Lage, eine Query intern in eine semantisch äquivalente Form umzuwandeln, die sich leichter optimieren lässt. Eine relativ einfache Option im Rahmen solcher Transformationen ist das Predicate Pushing, das eine einschränkende Bedingung, die für eine View (bzw. Inline-View) angegeben wird, an die in der View enthaltenen Sub-Queries überträgt. Dazu zunächst ein Beispiel mit Oracle:

-- 12.1.0.1
drop table t1;
drop table t2;

create table t1
as
select rownum id, lpad('*', 50, '*') col1
  from dual
connect by level <= 10000;

create table t2
as
select rownum id, lpad('*', 50, '*') col1
  from dual
connect by level <= 10000;

create index t1_idx on t1(id);
create index t2_idx on t2(id);
 
select *
  from (select *
          from t1
         union all
        select *
          from t2)
 where id <= 10;
 
------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                      |        |    20 |  1100 |     3   (0)| 00:00:01 |
|   1 |  VIEW                                 |        |    20 |  1100 |     3   (0)| 00:00:01 |
|   2 |   UNION-ALL                           |        |       |       |            |          |
|   3 |    TABLE ACCESS BY INDEX ROWID BATCHED| T1     |    10 |   550 |     3   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN                  | T1_IDX |    10 |       |     2   (0)| 00:00:01 |
|   5 |    TABLE ACCESS BY INDEX ROWID BATCHED| T2     |    10 |   550 |     3   (0)| 00:00:01 |
|*  6 |     INDEX RANGE SCAN                  | T2_IDX |    10 |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   4 - access("T1"."ID"<=10)
   6 - access("T2"."ID"<=10)
 
 
select * from t1 where id <= 10
 union all
select * from t2 where id <= 10;

-----------------------------------------------------------------------------------------------
| Id  | Operation                            | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |        |    20 |  1100 |     6  (50)| 00:00:01 |
|   1 |  UNION-ALL                           |        |       |       |            |          |
|   2 |   TABLE ACCESS BY INDEX ROWID BATCHED| T1     |    10 |   550 |     3   (0)| 00:00:01 |
|*  3 |    INDEX RANGE SCAN                  | T1_IDX |    10 |       |     2   (0)| 00:00:01 |
|   4 |   TABLE ACCESS BY INDEX ROWID BATCHED| T2     |    10 |   550 |     3   (0)| 00:00:01 |
|*  5 |    INDEX RANGE SCAN                  | T2_IDX |    10 |       |     2   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("ID"<=10)
   5 - access("ID"<=10)

Gesucht sind alle Datensätze mit einer id <= 10: in der ersten Query erfolgt diese Einschränkung für eine Inline-View, in der zweiten Query wird sie für beide angesprochenen Tabellen explizit angegeben. Der Blick auf den Ausführungsplan zeigt, dass die Einschränkung auch bei der Verwendung der Inline-View an die Basistabellen weitergeleitet wird: es ist also nicht so, dass die Klammerung dazu führen würde, dass erst die Menge aller Sätze aus t1 und t2 ermittelt werden müsste, ehe die Einschränkungen durchgeführt werden. Einen Unterschied gibt es allerdings zwischen den beiden Varianten: die Cost-Angabe für die Version mit der Inline-View ist nur halb so hoch, wie die für die beiden über UNION ALL verbundenen Queries. Außerdem ist für die Inline-View ein zusätzlicher Step VIEW enthalten, der aber keinen Einfluss auf den eigentlichen Zugriff haben sollte. Meiner Einschätzung nach ist die Kostenangabe für den zweiten Fall plausibler, da die Kosten der beiden Teiloperationen addiert werden sollten. Natürlich könnte sich ein solcher Unterschied im Rahmen einer komplexeren Query auf folgende Schritte auswirken.

Wie sieht der Fall nun für Postgres aus? Dazu ein entsprechendes Beispiel:

-- PostgreSQL 9.3.6
drop table t1;
drop table t2;

create table t1
as
with
basedata as (
select * from generate_series(1, 10000) id
)
select id
     , lpad('*', 50, '*')::text col2
  from basedata
;

-- manuelle Statistikerfassung
analyze t1;

create index t1_idx on t1(id);


create table t2
as
with
basedata as (
select * from generate_series(1, 10000) id
)
select id
     , lpad('*', 50, '*')::text col2
  from basedata
;

-- manuelle Statistikerfassung
analyze t2;

create index t2_idx on t2(id);

explain
select *
  from (select *
          from t1
         union all
        select *
          from t2) t
where id <= 10;

+------------------------------------------------------------------------+
|                               QUERY PLAN                               |
+------------------------------------------------------------------------+
| Append  (cost=0.29..16.88 rows=18 width=55)                            |
|   ->  Index Scan using t1_idx on t1  (cost=0.29..8.44 rows=9 width=55) |
|         Index Cond: (id <= 10)                                         |
|   ->  Index Scan using t2_idx on t2  (cost=0.29..8.44 rows=9 width=55) |
|         Index Cond: (id <= 10)                                         |
+------------------------------------------------------------------------+

explain
select * from t1 where id <= 10
union all
select * from t2 where id <= 10;

+------------------------------------------------------------------------+
|                               QUERY PLAN                               |
+------------------------------------------------------------------------+
| Append  (cost=0.29..17.06 rows=18 width=55)                            |
|   ->  Index Scan using t1_idx on t1  (cost=0.29..8.44 rows=9 width=55) |
|         Index Cond: (id <= 10)                                         |
|   ->  Index Scan using t2_idx on t2  (cost=0.29..8.44 rows=9 width=55) |
|         Index Cond: (id <= 10)                                         |
+------------------------------------------------------------------------+

Postgres kommt somit für beide Varianten exakt zum gleichen Plan.

Nun ist das Predicate Pushing für den Fall einer Inline-View mit über UNION ALL verknüpften Sub-Queries natürlich eher trivial, weil es auf der Hand liegt, dass alle Teiloperationen komplett getrennt behandelt werden können, da das Gesamtergebnis als Summe der Teilergebnisse definiert ist. Aber die Optimizer der großen RDBMS beherrschen noch sehr viele andere Tricks, die dafür sorgen können, dass die tatsächlich ausgeführte Operation deutlich anders aussieht, als das, was das abgesetzte SQL erwarten ließe. Für Oracle kann man die Evaluierung der Transformationen und ihr Ergebnis mit Hilfe eines CBO-Trace-Files (Event 10053) untersuchen - und für postgres könnte man sich den Optimizer-Code anschauen bzw. debuggen. In vielen Fällen genügt aber eine Blick auf den Ausführungsplan, um zu erkennen, was intern geschehen sein muss.

Dass es trotz dieser Transformationen immer noch sehr viele Fälle gibt, in denen sich relativ simple Änderungen in der Formulierung von SQL-Queries massiv auf den Ausführungsplan auswirken, steht auf einem anderen Blatt.

Montag, Mai 04, 2015

Ein Feature-Request: Actually Evaluated Rows

Vor ein paar Wochen habe ich im OTN-Forum an einer Diskussion teilgenommen, bei der sich einmal mehr gezeigt hat, dass die rowsource statistics in Oracles Trace-Instrumentierung zwar sehr viele wichtige Informationen liefern, aber doch ein paar interessante Angaben aussparen: die darin enthaltene A-Rows-Spalte (= actual rows) liefert die Anzahl der Datensätze, die als Ergebnis eines Schrittes an den folgenden Schritt im Ausführungsplan weitergegeben werden. Das ist natürlich sehr nützlich und gestattet durch den Vergleich von E-Rows (= estimated rows) und A-Rows die relevanten Fehler in der Kalkulation des Optimizers zu bestimmen (den Link auf Wolfgang Breitlings grundlegenden Artikel zum Thema spare ich mir diesmal). Allerdings gibt es Fälle, in denen nicht nur die Anzahl der Ergebnis-Sätze eine wichtige Information ist, sondern auch die Anzahl der Datensätze, die im Rahmen eines Schrittes betrachtet und dann gefiltert werden. Ein umfassendes Beispiel für einen solchen Fall hat Randolf Geist in seinem Artikel Combined ACCESS And FILTER Predicates - Excessive Throw-Away dargestellt (der das im OTN-Thread zugrunde liegende Problem modelliert): ein HASH JOIN erzeugt eine riesige - transiente - Menge von Datensätzen, die im gleichen Schritt gefiltert wird. Die zugehörige A-Rows-Angabe ist 1, aber der zwischenzeitlich erzeugte Suchraum umfasste 100M rows, was natürlich zu einer extremen CPU-Nutzung und einer langen Laufzeit führt.

Randolfs entsprechende Bemerkungen im OTN-Thread hatten mich dazu veranlasst, bei OTN in der Rubrik Database Ideas einen Vorschlag A second A-Row column for input rows in the traces zu formulieren, wobei diese zweite Spalte dann eher als AE-Rows (actually evaluated rows) zu bezeichnen wäre. Jonathan Lewis hat in einem Kommentar zur Idee darauf hingewiesen, dass im Fall die HASH JOINS eigentlich sogar zwei zusätzliche Werte interessant wären, weil hier aufgrund von Hash-Kollisionen unter Umständen sehr viel Arbeit bei der Ermittlung relevanter Matches anfallen kann, eher die ungefilterte Ergebnismenge des Joins erzeugt werden kann - ein passendes Beispiel dazu findet man in seinem Scratchpad. Sollte jemand a) der Meinung sein, dass diese Ergänzung nützlich wäre, b) einen OTN-Account besitzen und c) noch nicht abgestimmt haben, würde ich mich über Unterstützung dieser Idee freuen.