Ergebnisse des SQL Performance Tests


Im Jahr 2011 habe ich den „3-Minuten Test: Was weißt Du über SQL-Performance?“ gestartet. Der Test besteht aus fünf Fragen die alle dem selben Muster folgen: Jede zeigt ein Abfrage/Index-Paar und fragt, ob das gute Indizierung ist, oder nicht. Bis heute hat sich der Test zu einem der populärsten Features auf Use The Index, Luke entwickelt und wurde über 28.000 mal durchgeführt.

Beachte

Falls du neugierig geworden bist, mach den Test selbst, bevor du weiter liest. Ansonsten könnte dir dieser Artikel den Aha-Effekt verderben.

Obwohl der Test der Bildung dient, wollte ich wissen ob, man aus diesen 28.000 Ergebnissen interessante Erkenntnisse ableiten kann. Und ich denke, man kann. Man muss sich dabei aber einige Dinge vor Augen halten: Erstens verwendet der Test den Überraschungs-Faktor zur Steigerung der Aufmerksamkeit. Das heißt, es gibt drei Fragen, bei denen SQL und Index auf den ersten Blick gut aussehen, es aber nicht sind. Eine Frage macht es genau umgekehrt und zeigt eine SQL-Abfrage, die gefährlich aussieht, es aber dank des passenden Indexes nicht ist. Nur bei einer Frage stimmt der erste Eindruck mit dem tatsächlichen Sachverhalt überein. Der zweite Effekt, der die Aussagekraft der Ergebnisse beeinflussen könnte, ist, dass es keinerlei repräsentative Selektion der Teilnehmer gab. Jeder kann den Test machen. Auch mehrfach – und beim zweiten Mal potenziell ein besseres Ergebnis erzielen. Denke einfach daran, dass der Test nicht dafür ausgelegt war, wissenschaftliche Untersuchungen über das Indizierungs-Know-how in der Branche durchzuführen. Dennoch denke ich, dass die Menge der Daten groß genug ist, um einen Eindruck zu vermitteln.

Im Folgenden zeige ich für jede der fünf Fragen zwei Statistiken: Zuerst die Durchschnittsrate, mit der diese Frage richtig beantwortet wurde. Dann, wie diese Rate bei Nutzern von MySQL, Oracle, PostgreSQL und SQL Server Datenbanken abweicht. In anderen Worten zeigt es, ob MySQL-Nutzer mehr über Indizierung wissen als z.B. PostgreSQL-Nutzer. Vorgriff: Es ist genau anders herum. Der einzige Grund, warum ich in der glücklichen Lage bin, diese Daten zu zeigen, ist, dass der Test teilweise herstellerabhängige Syntax verwendet. Zum Beispiel verwenden MySQL und PostgreSQL das Schlüsselwort LIMIT, SQL Server aber TOP. Daher muss jeder Teilnehmer am Anfang des Tests eine Datenbank auswählen, damit die Fragen dann in der entsprechenden Syntax angezeigt werden.

Frage 1: Funktionen in der WHERE-Klausel

Ist die folgende SQL-Abfrage aus Performancesicht gut oder schlecht?

Eine Suche nach allen Einträgen im Jahr 2012:

CREATE INDEX tbl_idx ON tbl (date_column);

SELECT text, date_column
  FROM tbl
 WHERE TO_CHAR(date_column, 'YYYY') = '2012';

Bei dieser Frage wird die Funktion TO_CHAR benutzt, wie sie bei PostgreSQL und der Oracle-Datenbank Verwendung finden. Bei MySQL wird in der Frage stattdessen YEAR(date_column) beziehungsweise bei SQL Server datepart(yyyy, date_column) verwendet. Natürlich hätte ich EXTRACT(YEAR date_column) verwenden können, ich dachte mir aber, es ist am Besten die gängigste Variante zu zeigen.

Die Teilnehmer haben diese Optionen:

  • Gut: Es gibt keine groben Verbesserungen.

  • Schlecht: Eine grobe Verbesserung ist möglich.

Die richtige Antwort ist „schlecht,” weil der Index auf der Spalte date_column nicht verwendet werden kann, wenn man auf abgeleiteten Daten sucht. Falls du das nicht glaubst, sieh dir bitte die Proof-Scripts an oder die Erklärungen, die am Ende des Tests angezeigt werden. Diese beinhalten auch Links auf die entsprechenden Seiten auf Use The Index, Luke!

Wenn du nicht wusstest, dass Funktionen Indizes „abschalten“ können, bist du nicht alleine. Nur ungefähr zwei Drittel gaben die richtige Antwort. Wie bei jedem Multiple-Choice-Test muss man dabei aber daran denken, dass es eine gewisse Wahrscheinlichkeit gibt, zufällig die richtige Antwort zu erwischen. Bei nur zwei Optionen ist diese Chance mit 50/50 keineswegs vernachlässigbar. Ich habe diese „Rate-Quote“ in der folgenden Grafik hervorgehoben, um das zu verdeutlichen.

Es ist kein Zufall, dass ich diese Frage als Erste gewählt habe. Sie zeigt eines der häufigsten Probleme, das ich tagtäglich bei meiner Arbeit sehe. Dasselbe Problem kann übrigens auch mit VARCHAR-Feldern auftreten, wenn man zum Beispiel TRIM oder UPPER verwendet. Denke bitte immer daran: Wenn man in der where-Klausel Funktionen auf Spalten anwendet, ist ein Index auf der Spalte selbst für diese Abfrage nutzlos.

Obwohl das Ergebnis ziemlich enttäuschend ist – nicht viel besser als Raten –, ist es keine Überraschung für mich. Sehr wohl überraschend ist aber, wie das Ergebnis über die Nutzer verschiedener Datenbanken variiert.

Tatsächlich ist es so, dass MySQL-Benutzer nur zu 55% richtig geantwortet haben – kaum mehr als die Rate-Quote. PostgreSQL-Benutzer lagen dagegen zu 83% richtig.

Ein Effekt, der diese Abweichung erklären könnte, ist, dass MySQL keine funktions-basierte Indizierung unterstützt, PostgreSQL und die Oracle-Datenbank schon. Mit funktions-basierten Indizes kann man auch Ausdrücke wie TO_CHAR(date_column, 'YYYY') indizieren. Obwohl das nicht die empfohlene Lösung für dieses Problem ist, könnte das bloße Vorhandensein dieser Funktion das Bewusstsein für dieses Problem erhöhen. Bleibt die Frage, wie man damit das Ergebnis unter den SQL Server-Nutzern erklären kann. Und tatsächlich bietet SQL Server eine Lösung „in der Mitte“ an. Denn bei SQL Server kann man solche Ausdrücke zwar nicht direkt indizieren, aber eine sogenannte „computed column“ erstellen, die man dann indizieren kann.

Obwohl die Unterstützung von funktions-basierte Indizes das Ergebnis vielleicht erklären kann, ist es keine Entschuldigung. Die gezeigte Kombination von Index und Abfrage ist schlecht – völlig gleichgültig, ob die Datenbank funktions-basierte Indizierung unterstützt oder nicht. Und auch ohne funktions-basierter Indizierung ist eine grobe Verbesserung möglich:

SELECT text, date_column
  FROM tbl
 WHERE date_column >= TO_DATE('2012-01-01', 'YYYY-MM-DD')
   AND date_column <  TO_DATE('2013-01-01', 'YYYY-MM-DD');

Diese Lösung kann den ursprünglichen Index auf date_column unverändert verwenden. Weiters ist die Lösung auch sehr flexibel, weil man z.B. auch einzelne Tage, Wochen, Monate usw. abfragen kann. Das ist die empfohlene Lösung.

Offen bleibt wie viele der Teilnehmer, die richtig geantwortet haben, die sub-optimale Lösung mit einem funktions-basierten Index verwenden würden. Das würde ich nämlich bestenfalls als halbrichtig werten.

Frage 2: Indizierte Top-N Abfragen

Ist die folgende SQL-Abfrage aus Performancesicht gut oder schlecht?

Eine Suche nach dem aktuellsten Eintrag:

CREATE INDEX tbl_idx ON tbl (a, date_column);

SELECT id, a, date_column
  FROM tbl
 WHERE a = ? 
 ORDER BY date_column DESC
 LIMIT 1;

Beachte, dass das Fragezeichen als Platzhalter verwendet wird, weil ich immer darauf achte, Entwickler an die Benutzung von Bind-Parametern zu erinnern.

Als Antwort gibt es wieder diese beiden Optionen:

  • Gut: Es gibt keine groben Verbesserungen.

  • Schlecht: Eine grobe Verbesserung ist möglich.

Diese Abfrage zielt darauf ab, gefährlich auszusehen, obwohl sie es dank des passenden Indexes nicht ist. Generell scheinen die Leute zu glauben, dass order by die Daten immer sortieren muss. Mit dem passenden Index muss aber überhaupt keine Sortierung durchgeführt werden, sodass die Abfrage genau so schnell ist, wie eine Suche mit einem Primärschlüssel. Die genaue Erklärung findest du hier.

Das Ergebnis dieser Frage liegt ziemlich nahe an der Rate-Quote. Ich interpretiere das salopp als „die Leute habe keine Ahnung davon.“

Das ist besonders traurig, da ich in diesem Zusammenhang schon öfter Caching-Tabellen gesehen habe, die von einem Cron-Job regelmäßig gefüllt wurden, um eine Abfrage wie oben zu vermeiden. Interessanterweise wird der Cron-Job dann oft zum Problem, weil er in sehr kurzen Intervallen läuft, damit die Daten möglichst aktuell sind. Der richtige Index für die Top-N-Abfrage wäre oft die bessere Wahl gewesen.

Zu dieser Grafik muss ich anmerken, dass man bei der Oracle-Datenbank eine sehr spezielle Syntax braucht, um diesen Trick zu nutzen. Vor Version 12c (erschienen 2013) hatte die Oracle-Datenbank keine einfache Möglichkeit wie LIMIT oder TOP, um ein Ergebnis einzuschränken. Stattdessen muss man die Pseudo-Spalte ROWNUM verwenden:

SELECT *
  FROM (
        SELECT id, date_column
          FROM tbl
         WHERE a = :a
         ORDER BY date_column DESC
       )
 WHERE rownum <= 1;

Diese zusätzliche Komplexität könnte Benutzer der Oracle-Datenbank im Vergleich zu anderen verstärkt auf den falschen Weg gelockt haben.

Ein weiteres Argument, dass mir bezüglich dieser Frage oft entgegengebracht wird, ist, dass man die Spalte ID in den Index aufnehmen könnte, um einen Index-Only-Scan zu bewirken. Obwohl das grundsätzlich völlig richtig ist, würde ich nicht sagen, dass ein Index-Only-Scan hier eine große Verbesserung bringen würde – schließlich wird ohnehin nur eine Zeile aus der Tabelle gelesen. Natürlich gibt es Fälle, in denen man genau diese extra Performance braucht. Im Allgemeinen betrachte ich es in diesem Fall aber als premature optimization. Das ist natürlich nur meine Meinung. Wenn man diesem Argument folgt, könnte das aber erklären, warum PostgreSQL Nutzer wieder am besten abgeschnitten haben: PostgreSQL hatte vor Version 9.2, die im September 2012 erschien ist, keinen Index-Only-Scan. PostgreSQL Nutzer konnten also gar nicht in die Falle tappen, zu glauben ein Index-Only-Scan könnte grobe Verbesserungen bringen. „Grob“ ist hier Zugegebenermasen ein schwieriger Begriff.

Frage 3: Die Index-Spaltenreihenfolge

Ist die folgende SQL-Abfrage aus Performancesicht gut oder schlecht?

Zwei Abfragen, beide auf einer gemeinsamen Spalte:

CREATE INDEX tbl_idx ON tbl (a, b);

SELECT id, a, b
  FROM tbl
 WHERE a = ?
   AND b = ?;


SELECT id, a, b
  FROM tbl
 WHERE b = ?;

Und wieder dieselben Optionen:

  • Gut: Es gibt keine groben Verbesserungen.

  • Schlecht: Eine grobe Verbesserung ist möglich.

Die korrekte Antwort ist „schlecht,“ weil der Index nur von der ersten Abfrage optimal genutzt werden kann. Dreht man die Spaltenreihenfolge im Index um (b, a), könnten ihn beide Abfragen optimal nutzen. Eine genauere Erklärung dazu gibt es hier. Eine andere, aber schlechtere Lösung, wäre es einen zweiten Index auf (b) anzulegen. Leider weiß ich nicht, wie viele das als die „richtige“ Lösung angesehen hätten.

Das Ergebnis ist enttäuschend wie immer, aber wenig überraschend:

Das ist auch eines der Probleme, dem ich so gut wie jeden Tag begegne. Mehrspaltige Indizes werden einfach nicht richtig verstanden.

Bricht man das Ergebnis auf Datenbanken herunter, zeigt sich, dass sie alle recht eng beieinanderliegen. Das könnte daher kommen, das diese Frage nur sehr grundlegendes SQL verwendet, das bei allen Datenbanken gleich aussieht. In anderen Worten haben alle Teilnehmer genau den gleichen Code gesehen. Weiters gibt es bei diesem Fall auch keine weitläufig bekannten Datenbank-Spezialitäten, die das Ergebnis maßgeblich beeinflussen könnten. Weniger bekannte wie Oracle der SKIP SCAN könnten natürlich einen kleinen Einfluss haben. Generell könnte auch hier wieder das Index-Only-Scan Argument geltend gemacht werden, das allerdings diesmal zur „richtigen“ Beantwortung der Frage führt.

Insgesamt könnte es gut sein, dass dieses Ergebnis einfach nur zeigt, dass die Benutzer mancher Datenbanken mehr über Indizierung wissen als die anderer. Auffällig ist natürlich, dass PostgreSQL Nutzer zum dritten Mal am besten abschneiden.

Frage 4: LIKE-Suchen

Ist die folgende SQL-Abfrage aus Performancesicht unbedenklich oder problematisch?

In einem Textfeld suchen:

CREATE INDEX tbl_idx ON tbl (text);

SELECT id, text
  FROM tbl
 WHERE text LIKE '%TERM%';

Diesmal habe ich die Optionen anders formuliert:

  • Unbedenklich: Wird immer schnell sein.

  • Problematisch: Kann leicht zu Performanceproblemen führen.

Die richtige Antwort lautet „problematisch,“ weil der LIKE-Filter ein führendes Wildcard-Zeichen verwendet. Anderenfalls, wenn der Filter 'TERM%' wäre, könnte der Index sehr effizient verwendet werden. Eine anschauliche Erklärung dazu findest du hier.

Das Ergebnis dieser Frage macht Hoffnung. Diesmal traue ich mich zu sagen, dass die meisten Leute wissen, dass LIKE nicht für Volltextsuchen geeignet ist.

Die Auschlüsselung der Ergebnisse zeigt einen sehr schmalen Korridor:

Merkwürdig ist nur, dass PostgreSQL-Nutzer diesmal am schlechtesten abgeschnitten haben. Wenn man sich aber genau ansieht, wie diese Frage für PostgreSQL-Nutzer aussieht, könnte das eine Erklärung liefern:

CREATE INDEX tbl_idx ON tbl (text varchar_pattern_ops);

SELECT id, text
  FROM tbl
 WHERE text LIKE '%TERM%';

Beachte den Index-Zusatz varchar_pattern_ops. Bei PostgreSQL muss man diese Operator-Klasse angeben, damit der Index für LIKE-Abfragen ohne führendes Wildcard-Zeichen nutzbar ist (z.B. 'TERM%'). Da diese Frage darauf abzielt herauszufinden, ob die Leute das Problem mit dem führenden Wildcard-Zeichen kennen. Ohne diese Operator-Klasse gäbe es gleich zwei Gründe, warum der Index nicht genutzt werden kann: (1) wegen des führenden Wildcard-Zeichens; (2) wegen der fehlenden Operator-Klasse. Ich dachte, dann wäre es zu offensichtlich. Retrospektiv glaube ich aber, dass die Teilnehmer die Operator-Klasse eventuell als „Magie, die dafür sorgt, dass es doch funktioniert“ interpretieren und daher die falsche Antwort geben.

Frage 5a: Index-Only-Scan

Die fünfte Frage ist etwas trickreich, weil PostgreSQL den Index-Only-Scan nocht nicht unterstützte, als ich mir die Fragen ausdachte. Daher gibt es zwei Varianten von Frage 5: eine über den Index-Only Scan die MySQL, Oracle und SQL Server nutzern angezeigt wird, und eine weitere über die Spaltenreihenfolge im Index für PostgreSQL-Nutzer. Im folgenden werden beide Ergebnisse gezeigt, die Aufteilung nach Datenbank ist aus offensichtlichen Gründen aber eingeschränkt. Wir beginnen mit der Frage über den Index-Only-Scan:

Wie wird sich die Änderung auf die Performance auswirken?

Derzeitige Situation – liefert ca. hundert Zeilen aus der Tabelle mit einer Million Einträgen

CREATE INDEX tab_idx ON tbl (a, date_column);

SELECT date_column, count(*)
  FROM tbl
 WHERE a = 123
 GROUP BY date_column;

Geänderte Abfrage – liefert ca. zehn Zeilen aus der Tabelle mit einer Million Einträgen

SELECT date_column, count(*)
  FROM tbl
 WHERE a = 123
   AND b = 42
 GROUP BY date_column;

Beachte den zusätzlichen Filter in der where-Klausel der zweiten Abfrage.

Zu dieser Frage gibt es gleich vier mögliche Antworten:

  • Die Performance wird ungefähr gleich bleiben (+/- 10%)

  • Das hängt von den Daten ab

  • Die Abfrage wird deutlich langsamer (Auswirkung >10%)

  • Die Abfrage wird deutlich schneller (Auswirkung >10%)

Natürlich wusste ich beim erstellen des Tests, dass 50/50-Fragen dazu neigen das Ergebnis zu verfälschen. Diese Frage ist ein Kompromiss die Fragen und Antworten einerseits Verständlich und einfach zu halten (Fragen 1-4) und andererseits brauchbare Ergebnisse zu liefern.

Um es kurz zu machen: Die richtige Anwort lautet „die Abfrage wird deutlich langsamer.“ Das liegt daran, dass die erste Abfrage einen Index-Only-Scan ermöglicht – d.h. die Abfrage kann komplett aus dem Index beantwortet werden, ohne auf die eigentliche Tabelle zugreifen zu müssen. Die zweite Abfrage filtert zusätzlich auf einer Spalte, die nicht im Index enthalten ist. Daher muss die Datenbank jede potenziell passende Zeile aus der Tabelle laden, um die Bedingung auf der Spalte B zu prüfen. Das sind mindestens 100 extra Zugriffe auf die Tabelle um die 100 Zeilen zu laden, die bei der ersten Abfrage als Ergebnis geliefert wurden. Durch das group by sind es vermutlich mehr. Ein beachtlicher Mehraufwand, der die Abfrage deutlich langsamer macht. Eine umfassende Erklärung findest du hier.

Durch die höhere Anzahl an Optionen sinkt das Durchschnittsergebnis bei dieser Frage deutlich:

Ich denke aber noch immer, dass es falsch wäre zu sagen, dass 39% der Teilnehmer die richtige Antwort kannten. Sie haben zwar die richtige Antwort gegeben, es gibt aber noch immer eine 25%ige Wahrscheinlichkeit ohne es zu wissen die richtige Antwort zu geben.

Die Aufteilung nach Datenbanken ist ziemlich langweilig. Vermutlich wieder weil es keinerlei herstellerspezifische Syntax gab:

Da es diesmal vier Optionen gibt, macht es auch Sinn sich anzusehen wie oft die einzelnen Optionen gewählt wurden:

Das hat mich dann doch überrascht. Die Optionen „ungefähr gleich“ und „Datenbahängig“ haben jeweils ca. 25% erhalten – genau die Rate-Quote. Heißt das, dass die Hälfte der Teilnehmer geraten hat? Immerhin ist es die letzte Frage – einige Teilnehmer könnten einfach irgendeine Option gewählt haben, um Ihr Ergebnis zu sehen. Wie auch immer, die richtige Antwort „deutlich langsamer“ hat 38.8% erhalten. Offenbar auf Kosten der Option „deutlich schneller“, die nur in 10.9% der Fällen gewählt wurde.

Diese Frage zielt darauf ab, die Teilnehmer mit der Option „deutlich schneller“ in die Falle zu locken, da es doch schneller sein sollte, weniger Daten zu liefern – außer eben, wenn man dadurch einen Index-Only-Scan verhindert. Die einzige Theorie, die ich zu diesem Ergebnis habe, ist dass die Teilnehmer den Test bereits durchschaut haben, und auf Verdacht die überraschende Option gewählt haben. Das würde natürlich bedeuten, dass die Quote von 39% nichts über das Wissen diese Phänomens unter den Teilnehmern aussagt.

Einen weiteren Effekt, den ich hier erwartet hätte, ist dass es schließlich immer von den Daten abhängt. Natürlich gibt es Grenzfällen, bei denen die Performance ungefähr gleich bleiben würde – z.B. wenn alle benötigten Zeilen im selben Tabellenblock gespeichert sind. Das würde ich aber als äußerst unwahrscheinlich abtun – schon alleine deshalb, weil es dann von vornherein keinen Grund gäbe, die Spalte date_column überhaupt in den Index aufzunehmen.

Frage 5b: Index-Spaltenreihenfolge und Bereichs-Bedingungen

Diese Frage wird nur PostgreSQL-Nutzern gestellt:

Ist die folgende SQL-Abfrage aus Performancesicht gut oder schlecht?

Einträge im Status X suchen, die nicht älter als 5 Jahre sind.

CREATE INDEX tbl_idx ON tbl (date_column, state);

SELECT id, date_column, state
  FROM tbl
 WHERE date_column >= CURRENT_DATE - INTERVAL '5' YEAR
   AND state = 'X';

(365 rows)

Die Daten sind folgendermaßen verteilt:

SELECT count(*)
  FROM tbl
 WHERE date_column >= CURRENT_DATE - INTERVAL '5' YEAR;

 count 
-------
  1826

SELECT count(*)
  FROM tbl
 WHERE state = 'X';

 count 
-------
 10000

Der Index deckt beide Spalten der where-Klausel ab. Einer der Filter verwendet den Ist-gleich-Operator, der andere einen Größer-gleich-Operator. Verwendet man jeweils nur einen Filter wird das Ergebnis deutlich größer.

Die möglichen Antworten sind:

  • Gut — Wird immer schnell sein.

  • Schlecht — Kann schnell zu Performanceproblemen führen.

Die richtige Antwort ist „schlecht,“ weil die Spaltenreihenfolge im Index falsch ist. Die Regel, dass Index-Spalten von links her verwendet werden können, stimmt nämlich nur bei Ist-gleich-Bedingungen. Ab der ersten Bereichs-Bedingung ist die Kette unterbrochen und nachfolgende Spalten könnten nicht mehr optimal genutzt werden. Mit optimal meine ich als Zugriffsprädikat. Eine anschaulichere Erklärung findest du hier.

Mit dem Index von oben muss die Datenbank 1826 Einträge aus dem Index laden (jene, die der Bedingung auf date_column entsprechen) und jeden davon auf den Status prüfen. Dreht man die Spaltenreihenfolge um, kann die Datenbank mit dem Index direkt auf die 365 gesuchten Zeilen zugreifen.

Und so haben die Teilnehmer geantwortet:

Moment, das ist unter der Rate-Quote! Offenbar ist es nicht nur so, dass es die Teilnehmer nicht wissen, sondern sogar vom Gegenteil überzeugt sind. Aber natürlich muss ich hier wieder eingestehen, dass die Formulierung der Frage schwammig ist. Meine Messung hat in diesem Fall 70% Verbesserung gebracht, wenn man die Spalten umdreht.

Gesamtergebnis: Wie viele haben bestanden?

Natürlich ist es interessant, die Fragen jeweils einzeln zu betrachten, das sagt aber nichts darüber aus, wie viele Teilnehmer z.B. alle fünf Fragen richtig beantwortet haben. Diese Information ist in der folgenden Abbildung dargestellt.

Zu guter Letzt möchte ich diesen Chart noch auf eine einzige Zahl herunterbrechen: Wie viele der Teilnehmer haben den Test „bestanden?“

Wenn man bedenkt, dass der Test nur fünf Fragen hat und vier davon 50/50-Fragen sind, glaube ich, dass man sagen kann, drei richtige Antworten sind zu wenig um den Test zu bestehen. Fünf richtige Fragen zu fordern wäre natürlich etwas viel verlangt. Daher scheint es sinnvoll, mindestens vier richtige Antworten zu erfordern, um den Test zu bestehen. Entsprechend dieser Definition haben nur ihn nur 38.2% bestanden. Die Wahrscheinlichkeit den Test durch Raten zu bestehen ist dabei noch immer 12.5%.

Wenn dir dieser Artikel gefällt und du richtiges Indizieren lernen willst, ist SQL Performance Explained genau das Richtige für dich.

Über den Autor

Photo of Markus Winand
Markus Winand stimmt Entwickler auf SQL-Performance ein. Er hat das Buch SQL Performance Explained veröffentlicht und bietet inhouse Schulungen sowie Tuning-Leistungen auf http://winand.at/ an.
comments powered by Disqus