Montag, Juni 30, 2014

Datenmodellierungsverfahren für das Core Data Warehouse

Wieder nur ein Link: aus dem Hause Trivadis stammt das white paper Comparison of Data Modeling Methods for a Core Data Warehouse, geschrieben vom Autorenteam Maren Eschermann, Adriano Martino und Dani Schnider (dem ich gelegentlich versprochen habe, seinen Namen nicht mehr falsch zu schreiben). Das Papier erläutert die folgenden Modellierungsverfahren für die Core-Schicht des DWHs (also jene Ebene, die für die Integration von Daten aus unterschiedlichen Quellsystemen und für die Historisierung der Daten zuständig ist):
  • Dimensional Core Modeling with dimensions and fact tables (basierend auf Kimballs Methodologie)
  • Relational Core Modeling with master data versioning
  • Data Vault Modeling with Hubs, Satellites and Links (der geistige Urheber dieser Idee ist Dan Linstedt)
Dargestellt werden die Unterschiede hinsichtlich der Komplexität der Architektur, der Besonderheiten im ETL-Prozess, in der Historisierung und im Lifecycle-Management, sowie der Eigenschaften bei der Überführung der Core-Daten in die Data Marts - und besonders nützlich finde ich die Zusammenfassung im Abschnitt 7 mit einer Vergleichsmatrix zu den zentralen Punkten der Untersuchung und mit der Erklärung, welches Modell unter welchen Bedingungen seine besonderen Stärken (bzw. Schwächen) zeigt.

Freitag, Juni 27, 2014

PDF über PL/SQL erzeugen

Zur Unterstützung meines unterstützungsbedürftigen Gedächtnisses: Morton Braten zeigt einige Möglichkeiten zur Erzeugung von PDF-Reports über PL/SQL.

Sonntag, Juni 22, 2014

Verwendung von SQL Server Query Hints

In der SQL Server Welt betrachtet man Query Hints offenbar ähnlich argwöhnisch wie im Oracle Umfeld - als "best pratice" gilt: man sollte sie vermeiden und alle Entscheidungen dem Optimizer überlassen. Thomas Kejser sieht das ein wenig anders:
For a complete novice of SQL Server, I would agree. But to call avoiding query hints a “best practise” is taking it too far. For anyone with a little experience who knows what they are doing, I find that query hints are not only a good reactive solution, they are a proactive, design time, tool. If I find a stored procedure or query that either
  • A) Gets run often… or
  • B) Is very important and needs to be consistent (“important” is nearly always the same as consistent)
Putting it in simple term: I don’t trust the optimiser with these types of queries. Mission critical queries are much too important to leave in the hands of an unpredictable process like a query optimiser.
Im Anschluss liefert der Autor dann noch eine Klassifikation von Hints, die er einsetzen, und solchen, die er in der Regel vermeiden würde (darunter auch explizite Index-Hints).

Möglicherweise würde ich ein wenig mehr als "a little experience" voraussetzen und auf eine gute Dokumentierung solcher Hints bestehen, aber grundsätzlich sehe ich den Fall ähnlich wie der Herr Kejser.

Freitag, Juni 20, 2014

Rowsource-Statistiken nach Query-Abbruch

Ein nützlicher Hinweis von Randolf Geist im OTN-Forum:
one thing that is not so commonly known about the Rowsource Statistics is that *no matter how* the statement execution completed, the data will be available. So a long running query can be cancelled and the Rowsource Statistics will still be populated (but only after the statement execution stopped in some way). Usually the information then available is already good enough to tell why the execution takes so long.
Und damit ich mir das Verhalten besser merken kann, ein kleines Beispiel dazu:

drop table test;

create table test
as
select rownum id
  from dual
connect by level <= 100000;

-- ein cross join, der auf meinem Rechner einige Zeit unterwegs ist
select /*+ gather_plan_statistics */ count(*)
  from t t1, t t2;

Wenn man nach dem Start der Abfrage in einer zweiten Session die rowsource-Statistiken abrufen möchte, erhält man folgendes Resultat:

select *
  from table(dbms_xplan.display_cursor('6jn221223mda9', 0, 'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------
SQL_ID  6jn221223mda9, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*)   from t t1, t t2

Plan hash value: 840385502

--------------------------------------------------------------------------
| Id  | Operation             | Name | E-Rows |  OMem |  1Mem | Used-Mem |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |        |       |       |          |
|   1 |  SORT AGGREGATE       |      |      1 |       |       |          |
|   2 |   MERGE JOIN CARTESIAN|      |     10G|       |       |          |
|   3 |    TABLE ACCESS FULL  | T    |    100K|       |       |          |
|   4 |    BUFFER SORT        |      |    100K| 73728 | 73728 |          |
|   5 |     TABLE ACCESS FULL | T    |    100K|       |       |          |
--------------------------------------------------------------------------

Note
-----
   - Warning: basic plan statistics not available. These are only collected when:
       * hint 'gather_plan_statistics' is used for the statement or
       * parameter 'statistics_level' is set to 'ALL', at session or system level

Die Statistiken sind also während der Ausführung nicht verfügbar. Aber wenn ich die Query in sqlplus nach einiger Zeit mit ctl+c terminiere, bekomme ich die bis zum Abbruch gesammelten Statistiken:

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------------
SQL_ID  6jn221223mda9, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*)   from t t1, t t2

Plan hash value: 840385502

-------------------------------------------------------------------------------------------------------------------
| Id  | Operation             | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |      |      1 |        |      0 |00:00:00.01 |       0 |       |       |          |
|   1 |  SORT AGGREGATE       |      |      1 |      1 |      0 |00:00:00.01 |       0 |       |       |          |
|   2 |   MERGE JOIN CARTESIAN|      |      1 |     10G|    542M|00:01:12.22 |     901 |       |       |          |
|   3 |    TABLE ACCESS FULL  | T    |      1 |    100K|   5423 |00:00:00.03 |      49 |       |       |          |
|   4 |    BUFFER SORT        |      |   5423 |    100K|    542M|00:00:37.74 |     852 |  2675K|   740K| 2377K (0)|
|   5 |     TABLE ACCESS FULL | T    |      1 |    100K|    100K|00:00:00.01 |     852 |       |       |          |
-------------------------------------------------------------------------------------------------------------------


17 Zeilen ausgewählt.

Nach 5423 Schleifen ist die Datenbank also von ihrem nutzlosen Unterfangen erlöst worden - und die Analyse kann beginnen.

Dienstag, Juni 17, 2014

Autonomes Logging mit postgres

Im Rahmen von ETL-Operationen ergibt sich immer wieder die Situation, dass man eine Log-Message in ein ETL-Log schreiben möchte, ohne dadurch die eigentliche Transaktion per commit festzuschreiben, denn der Umfang der Transaktionen sollte nicht durch die Log-Einträge bestimmt werden. Da die Log-Nachricht im Fall eines Rollbacks der Transaktion ebenfalls zurückgerollt werden würde, kann man sich allerdings auch nicht auf den Abschluss der eigentlichen ETL-Operation verlassen. Zur Lösung dieses Problems bietet Oracle das Pragma Autonomous Transaction an, mit dessen Hilfe man eine untergeordnete Transaktion unabhängig von der rahmenden Transaktion abschließen kann.

Leider bietet postgres kein entsprechendes Feature an, so dass man sich dort mit einem Workaround - man könnte auch sagen "horrible hack" - behelfen muss: man schreibt die Log-Informationen über einen loopback DB-Link in eine Log-Tabelle; und da der Zugriff via DB-Link außerhalb der Transaktion liegt, führt ein Rollback der rahmenden Transaktion nicht mehr zum Rollback der Log-Nachricht:

postgres=# create extension dblink;
CREATE EXTENSION

postgres=# create table log_table(message text);
CREATE TABLE

postgres$# begin
postgres$#     perform dblink_connect('log_link','dbname=postgres');
postgres$#     perform dblink_exec('log_link','insert into log_table(message) values (''' || message || ''');');
postgres$#     perform dblink_exec('log_link','commit;');
postgres$#     perform dblink_disconnect('log_link');
postgres$# end;
postgres$# $$
postgres-# language plpgsql
postgres-# ;
CREATE FUNCTION

postgres=# select insert_log_message('123');
 insert_log_message
--------------------
(1 Zeile)

postgres=# select * from log_table;
 message
---------
 123
(1 Zeile)

Und jetzt noch der Beweis, dass eine auf diesem Weg aufgerufene Sub-Transaktion tatsächlich das Rollback der rahmenden Transaktion überlebt:

-- fehlerhafter Code
postgres=# create or replace function test_transaction_handling() returns void
postgres-# as
postgres-# $body$
postgres$#
postgres$# begin
postgres$#
postgres$# perform insert_log_message('ueberlebt rollback');
postgres$#
postgres$# execute 'select ,,,';
postgres$#
postgres$# end
postgres$# $body$
postgres-# LANGUAGE plpgsql
postgres-# ;
CREATE FUNCTION

-- Aufruf mit Fehler
postgres=# select test_transaction_handling();
ERROR:  syntax error at or near ","
ZEILE 1: select ,,,
                ^
ANFRAGE:  select ,,,
KONTEXT:  PL/pgSQL function test_transaction_handling() line 7 at EXECUTE statement

-- aber die Log-Nachricht wurde geschrieben:
postgres=# select * from log_table;
      message
--------------------
 123
 ueberlebt rollback

Schön ist das natürlich nicht, aber es funktioniert. Allerdings würde ich Neil Conway zustimmen, der auf den Vorschlag dieses Hacks im postgres-Forum antwortete:
I agree that autonomous transactions would be useful, but doing them via dblink is a kludge. If we're going to include anything in the core database, it should be done properly (i.e. as an extension to the existing transaction system).
Bis zur Ergänzung des Features (oder sonst einer besseren Lösung) bin ich mit diesem "Workaround" aber erst mal zufrieden. 

NULL-Werte in Indizes

Jonathan Lewis hat in seinem Blog zuletzt zwei Artikel veröffentlicht, die sich mit der Speicherung von NULL-Werten in Indizes beschäftigen:
  • Bitmap Nulls: liefert ein kompaktes Beispiel für die Möglichkeit der Verwendung von Bitmap-Indizes bei der Einschränkung über ein IS NULL Prädikat: bekanntlich enthält ein B*Tree-Index keine Einträge für Datensätze, bei denen alle indizierten Spalten einen NULL-Wert enthalten - während im Bitmap Index ausnahmslos jeder Datensatz indiziert wird. Somit kann eine Bedingung column IS NULL im Ausführungsplan als access predicate für einen Bitmap-Index-Zugriff erscheinen. Das ist jetzt in keiner Weise neu, aber der Herr Lewis versteht seinen Artikel als Referenz - und in dieser Funktion würde ich gelegentlich auch darauf verweisen.
  • Cluster Nulls: erläutert, dass auch Cluster Indizes Einträge speichern, bei denen alle indizierten Attribute NULL enthalten - obwohl ein Cluster Index ein B*Tree-Index ist. Als Beleg wird ein Dump des Index-Blocks vorgelegt, der die NULL-Werte enthält: allerdings verwendet der Optimizer einen solchen Index nicht für den Zugriff auf reine NULL-Einträge in User-Queries (auch nicht, wenn man ihn durch Hints dazu zwingen will; nur für interne Operationen wird er herangezogen). Als möglichen Workaround schlägt der Autor einen ergänzenden FBI vor, der die NULL-Werte auf einen Wert mapped, der in den Daten nicht vorkommen kann - was allerdings eine Änderung der Queries erforderlich macht.

Donnerstag, Juni 12, 2014

Hadoop-Einführung für Oracle-Nutzer

Eine kompakte und gut verständliche Einführung zum Thema Hadoop for Oracle Professionals hat Arup Nanda veröffentlicht, der darin diverse BigData-Termini und -Konzepte in die Sprache eines RDBMS-Benutzers übersetzt. 

Mittwoch, Juni 11, 2014

Interpretation von AWR Reports

Ich behaupte zwar bisweilen allerlei - aber nicht, ein Experte auf dem Gebiet der Interpretation von AWR-Reports zu sein. Normalerweise schaue ich fast ausschließlich auf die Query-Abschnitte. Aber wenn ich eine detaillierte Interpretation eines AWR-Reports durchführen müsste, dann würde ich mich an der von Jonathan Lewis in seinem Blog erstellten Link-Sammlung orientieren. Eine hübsch gestaltete einführende Präsentation zum Thema gibt's von Franck Pachot.

Montag, Juni 09, 2014

Bestimmung der Log-Writer-Aktivität mit strace

Craig Shallahamer zeigt in einem Artikel und einem zugehörigen Mini-Video (Laufzeit 3:11 min) mit Hilfe eines strace-Aufrufs, dessen Ergebnisse er in eine Log-Datei schreiben lässt, dass der log-Writer-Prozess LWGR auch in 12c in einem beschäftigungslosen System alle drei Sekunden aufgeweckt wird, um zu prüfen, ob es etwas zu erledigen gibt. Im Trace-File findet sich regelmäßig ein Aufruf der Semaphoren-Operation semtimedop, die dazu dient einen Prozess für eine bestimmte Zeit in den sleep-Modus zu versetzen, sofern er nicht vorher durch ein anderes Ereignis aufgeweckt wird.

Ich finde es ausgesprochen sinnvoll, solche Tests grundlegender Verhaltensweisen für neue Releases durchzuführen, da sich der grundlegenden Elemente zwar selten verändern - aber wenn sie es tun, dann sollte man sich davon nicht überraschen lassen.

Montag, Juni 02, 2014

Neue Auflage für Troubleshooting Oracle Performance

Eines der besten Bücher zum Thema "Oracle Performance" ist Christian Antogninis Troubleshooting Oracle Performance, dessen zweite Auflage dieser Tage erschienen ist. Offenbar handelt es sich um eine recht umfangreiche Überarbeitung, weshalb ich davon ausgehe, dass sich die Anschaffung auch für Käufer der ersten Auflage noch einmal lohnt.

Nachtrag 07.06.2014: Zu den Unterschieden der beiden Auflagen hat der Autor einen eigenen Blog-Eintrag veröffentlicht.

Sonntag, Juni 01, 2014

Flame Graphs zur Analyse der Entwicklung des Oracle Optimizer

Luca Canali kombiniert in seinem Artikel Oracle Optimizer Investigated with Flame Graphs die von Brendan Gregg entwickelte Visualisierungstechnik der Flame Graphs mit den von Tanel Poder zusammengetragenen Informationen zur stack trace Analyse im Rahmen des Troubleshootings in Oracle Datenbanken. Dabei zeigt er anhand eines überschaubaren Beispiels die unterschiedlichen Graphen, die sich für eine Query ergeben, die zunächst mit dem rule based optimizer (RBO), dann mit dem cost based optimizer (CBO) der Version 11 und schließlich mit dem cost based optimizer der Version 12 (mit adaptive optimization) optimiert wird. Die zentrale Beobachtung dabei ist, dass der im Rahmen des Parsings zu absolvierende code path immer länger und komplexer wird: während der RBO sich an sein statisches Regelwerk halten konnte, verwendet der CBO zahlreiche Prüfungen, Transformationen und Fallunterscheidungen, die die Optimierung aufwändiger machen, aber in der Regel zu einem besseren Ergebnis führen - wobei die Verfahren von Version zu Version immer elaborierter werden. Das ist jetzt kein besonders überraschendes Ergebnis, aber die Visualisierung der unterschiedlichen Vorgehensweisen mit Hilfe der Flame Graphs lässt die Unterschiede ausgesprochen anschaulich werden.