• JOIN ON vs. WHERE

    von am 6. November 2008

    “Soll man bei einem SELECT-Statement, das mehrere Tabellen benötigt die Verbund-Kriterien in einem expliziten JOIN ON oder in der Selektion angeben?”

    Ist das nicht auch eine der Fragen, die Glaubenskriege unter Entwicklern auslösen kann? Zumindest habe ich schon das eine oder andere Mal Diskussionen über dieses Thema mitbekommen.

    Natürlich kann man keine allgemeingültige Aussage für alle RDBM-Systeme machen. Wir wollen dieses Thema konkret im Beispiel von MySQL 5.0 etwas näher betrachten.

    Betrachten wir dazu zwei Tabellen, user und entry:

    CREATE TABLE `user` (
      `uid` int(11) NOT NULL,
      `name` varchar(20) default NULL,
      PRIMARY KEY  (`uid`)
    ) ENGINE=MyISAM;
    CREATE TABLE `entry` (
      `uid` int(11) default NULL,
      `title` varchar(255) default NULL,
      `entry` text,
      INDEX idx_uid (uid)
    ) ENGINE=MyISAM;

    Stellen wir uns vor, wir möchten die Einträge aller User anzeigen. Der “Old-School-Join”, also die SQL-87-Syntax sieht demnach für einen Join von user auf entry folgendermaßen aus:

    SELECT user.uid, user.name, entry.title, entry.entry FROM user, entry WHERE user.uid = entry.uid;

    Das SQL-92-Pendant sieht folgendermaßen aus:

    SELECT user.uid, user.name, entry.title, entry.entry FROM user JOIN entry ON (user.uid = entry.uid);

    Gängige Aussagen von Entwicklern:

    1. “SQL-87-Syntax ist langsamer”
    2. “Die SQL-87-Syntax ist fehleranfälliger” (vergessen des Kriteriums in der Selektion)
    3. “Ich finde die SQL-92-Syntax unbequem”
    4. “Mit der SQL-87-Syntax kann man nicht alles erreichen” (Left Outer Join, etc.)

    Wollen wir die Aussagen unter die Lupe nehmen:

    1. SQL-87-Syntax ist langsamer

    MySQL bietet mit EXPLAIN eines der besten bereits eingebauten Analysetools um dem Query Optimizer auf die Finger zu schauen. Hat man den Output dieser Funktion verstanden, ist einem sehr geholfen wenn man Performanceprobleme bei SQL Queries suchen muss.

    Um die Frage zu beantworten machen wir uns EXPLAIN zu Nutze:

    mysql> explain SELECT user.uid, user.name, entry.title, entry.entry FROM user, entry WHERE user.uid = entry.uid;
    +----+-------------+-------+------+---------------+---------+---------+---------------------+------+-------------+
    | id | select_type | table | type | possible_keys | key     | key_len | ref                 | rows | Extra       |
    +----+-------------+-------+------+---------------+---------+---------+---------------------+------+-------------+
    |  1 | SIMPLE      | user  | ALL  | PRIMARY       | NULL    | NULL    | NULL                |  100 |             |
    |  1 | SIMPLE      | entry | ref  | idx_uid       | idx_uid | 5       | phphatesme.user.uid |    1 | Using where |
    +----+-------------+-------+------+---------------+---------+---------+---------------------+------+-------------+
    2 rows in set (0.00 sec)
    mysql> explain SELECT user.uid, user.name, entry.title, entry.entry FROM user JOIN entry ON (user.uid = entry.uid);
    +----+-------------+-------+------+---------------+---------+---------+---------------------+------+-------------+
    | id | select_type | table | type | possible_keys | key     | key_len | ref                 | rows | Extra       |
    +----+-------------+-------+------+---------------+---------+---------+---------------------+------+-------------+
    |  1 | SIMPLE      | user  | ALL  | PRIMARY       | NULL    | NULL    | NULL                |  100 |             |
    |  1 | SIMPLE      | entry | ref  | idx_uid       | idx_uid | 5       | phphatesme.user.uid |    1 | Using where |
    +----+-------------+-------+------+---------------+---------+---------+---------------------+------+-------------+
    2 rows in set (0.00 sec)

    Wie man erkennt, hat MySQL exakt den gleichen Execution Plan für dieses SQL Query festgelegt. In beiden Fällen hat es erkannt, dass es sich hier um einen Tabellenverbund mit einer “=”-Relation zwischen den Spalten, die als Verbundkriterium angegeben wurden, handelt und der KEY idx_uid, der dazu verwendet werden kann wird für das Query ausgenutzt.

    Abgesehen von der Tatsache, dass das 2. Query 2 Bytes länger ist und deshalb in Theorie etwas schneller geparst werden kann und bei vielen Joins auch schneller übers Netz übertragen werden kann müssen die Queries gleich schnell laufen – und das tun sie auch. (Man bedenke, ich bevorzuge die optionalen Klammern im ON immer zu schreiben, ansonsten wären beide Queries sogar gleich auf in Sachen Byte-Anzahl.)

    2. “Die SQL-87-Syntax ist fehleranfälliger” (vergessen des Kriteriums in der Selektion)

    Führt man das SQL-87-Query ohne Verbundkriterium aus, erhält man als Ergebnis das Kreuzprodukt beider Tabellen.

    SELECT user.uid, user.name, entry.title, entry.entry FROM user, entry;

    Das ist jedoch in der Regel nicht das, was man eigentlich wollte. Da jedoch das Query vollkommen korrektes SQL ist wird MySQL kein Warning anzeigen sondern brav die (fehlerhafte) Anweisung befolgen.

    Die SQL-92-Variante wird deshalb gerne empfohlen. Diese kann jedoch auch ohne Verbundkriterium aufgerufen werden:

    SELECT user.uid, user.name, entry.title, entry.entry FROM user JOIN entry;

    Das Ergebnis hierbei ist ebenfalls das Kreuzprodukt der Tabellen; wie zu erwarten auch hier kein Warning – es könnte ja auch so gewünscht sein.

    Bei beiden Varianten kann also das Verbundkriterium vergessen werden, was dazu führen kann, dass überdimensional große Kreuzprodukte entstehen können, die den Betrieb der Datenbankserver ernsthaft gefährden können.

    (Im Fall von MyISAM als Storage Engine blockiert ein lange laufendes SELECT Statement sobald ein INSERT/UPDATE/DELETE-Statement auf die Tabelle durchgeführt werden soll alle Statements, die diese Tabelle mit einbeziehen. Das führt zu wartenden Apache-Prozessen, die auf das SELECT Statement warten müssen und nach kurzer Zeit dazu, dass der Apache keine Apache-Prozesse mehr frei sind. Goodbye Lenin.)

    Ein Hoffnungslicht für JOIN ON-Freunde: Ein kleiner Vorteil von JOIN ON ist, dass man beim Betrachten einfach sehen kann, dass kein Verbundkriterium angegeben ist. Bei einem SQL-87-Join mit vielen Tabellen kann es leichter passieren, den Überblick zu verlieren.

    3. “Ich finde die SQL-92-Syntax unbequem”

    Solche Argumente finden den Weg in Diskussionen, sobald man sich nicht mehr schlagkräftiger Argumente bedienen kann. In der Tat ist es so, dass der Entwickler die JOIN-Syntax bei SQL-87 gar nicht kennen muss. Alles kann mit “handwerkszeug” erledigt werden. SELECT, Projektion, Tabellennamen kommagetrennt, Selektion. Aber handelt es sich hier um schwarze Magie? Die hohe Kunst der JOINs? Ich glaube nicht.

    4. “Mit der SQL-87-Syntax kann man nicht alles erreichen” (Left Outer Join, etc.)

    MySQL verfügt meines Wissens in der aktuellen Version nicht über die Möglichkeit, OUTER JOINs in der SQL-87-Syntax anzugeben. Einige RDBM-Systeme wie Oracle verwenden hierfür die Notation (+).

    Auch ein NATURAL JOIN kann in der SQL-87-Syntax bei MySQL nicht erreicht werden.

    Fazit:

    Einige Argumente sind einfach falsch, weder die Eine, noch die Andere Variante hat hier gepunktet. (1,2,3)

    Argument 4 spricht eindeutig für die konsistente Verwendung von SQL-92 und bringt es hiermit als Sieger aus dem Rennen.

    MySQL erlaubt bei mehr als 2 Tabellen im Verbund auch den “Mixed Style”. Das bedeutet, dass swohl die SQL-87-Syntax, als auch die SQL-92-Syntax verwendet wird.

    Beispiel:

    SELECT
     user.uid, user.name, entry.title, entry.entry, auth.password
    FROM
     user JOIN auth ON (user.uid=auth.uid),
     entry 
    WHERE user.uid = entry.uid;

    Hier ist offensichtlich, dass das nur im Chaos enden kann. Hat man im Nachhinein beim 2. Überblicken des Queries wirklich noch im Auge, für welchen Verbund das Kriterium schon angegeben ist und für welche nicht?

    Erschwerenderweise kommt hinzu dass wohl MySQL die Priorität bei der Bindung irgendwo beim Versionswechsel mitten in 5.0.x geändert hat. So kann das Query jetzt eine ganz andere Semantik besitzen.

    (Falls jemand hierfür eine offizielle Quelle hat, bitte hier als Kommentar posten. Meine bisherige Source ist lediglich Mundpropaganda…)

    Auf jeden Fall spricht auch das dafür, konsequent SQL-92 einzusetzen.

    Timo Holzherr

    Software-Entwicklung ist für mich mehr als ein Beruf, mit dem ich mir die Brötchen verdiene - es ist meine Leidenschaft. Themen wie professionelle, objektorientierte Software-Entwicklung, moderne Web-Entwicklung, ...

    Zum Profil von Timo Holzherr

    9 Kommentare »


    • danielj
      am 6. November 2008 um 08:23 Uhr

      Für mich das wichtigste Kriterium pro SQL-92 ist ganz klar die deutlich bessere Les-/Wartbarkeit, da JOIN und Bedingung mit einem Blick erfasst werden können. Das ist vor allem bei komplexeren Abfragen ein immenser Vorteil.


    • Nils Langner
      am 6. November 2008 um 09:09 Uhr

      Wenn ich ehrlich bin, habe ich Argument 1 auch immer für wahr gehalten. Naja man lernt nie aus.


    • Salz`
      am 6. November 2008 um 09:41 Uhr

      1. Argument: Ich habe es auch für wahr gehalten, und ich würde auch immer noch glauben das es auf die Implementierung der Datenbank ankommt.
      3. Argument: Nein, ich empfinde die SQL-92 Syntax um einiges Bequemer als die SQL-87 Variante. Schön gemütlich nach und nach per “JOIN ….” Informationen anhängen ist doch super bequem!

      ps: Sollte “Fazit” nicht auch eine Überschrift sein?


    • Timo Holzherr
      am 6. November 2008 um 10:24 Uhr

      Hi Salz`,

      1) Wie ich schon geschrieben hatte (“Natürlich kann man keine allgemeingültige Aussage für alle RDBM-Systeme machen. Wir wollen dieses Thema konkret im Beispiel von MySQL 5.0 etwas näher betrachten.”) kann dieses Aussage natürlich nur für MySQL gemacht werden. Angenommen, ich implementiere morgen mein eigenes RDBMS kann ich schlussendlich implementieren, was ich will. Auch, dass bspw. die SQL-87-Variante langsamer läuft.

      2) Das sehe ich genauso. Trotzdem hört man es immer noch hin und wieder…

      3) Habe “Fazit” zur Überschrift gemacht. Danke ^^


    • Timo Holzherr
      am 6. November 2008 um 10:36 Uhr

      Wer übrigens von EXPLAIN nicht überzeugt wurde, dass beide Queries gleich ausgeführt werden, kann die letzte Sicherheit durch ein EXPLAIN EXTENDED bekommen:

      Explain Extended gibt als Warning nach dem Optimieren das Query so aus, wie es später ausgeführt wird.

      - EXPLAIN EXTENDED SELECT …;
      - SHOW WARNINGS;

      Ich habe mal beide Queries mit EXPLAIN EXTENDED ausgewertet:

      http://timo.holzherr.de/mysql_tee_join_vs_where.txt

      Jetzt haben wir es noch schwärzer auf weiß – die Queries sind identisch…


    • Voo
      am 7. November 2008 um 14:47 Uhr

      Hi Timo,

      die Quelle ist das online MySQL reference manual:

      http://dev.mysql.com/doc/refman/5.0/en/join.html

      “Join Processing Changes in MySQL 5.0.12″
      [...]
      “Previously, the comma operator (,) and JOIN both had the same precedence, so the join expression t1, t2 JOIN t3 was interpreted as ((t1, t2) JOIN t3). Now JOIN has higher precedence, so the expression is interpreted as (t1, (t2 JOIN t3)).”

      Grüße,
      Voo


    • Timo
      am 7. November 2008 um 15:32 Uhr

      Hi,

      vielen Dank für die Quelle – grauenvoll! Es empfiehlt sich also sehr, keinen Mixed Style anzuwenden! *Puh*

      Grüße,
      Timo


    • Dennis
      am 15. Januar 2009 um 10:01 Uhr

      Das Problem hierbei ist, dass der MySQL Query Optimizer die 1. Query einfach ändert, sonst würde MySQL einen Full Table Scan machen – daher kommen die gleichen Ergebnisse zustande. Versucht das doch mal mit 3 Tabellen, ob das der Query Optimizer immernoch hinbekommt – ich denke dem sind da auch Grenzen gesetzt.


    • UND anstelle von JOIN - php.de
      am 25. August 2009 um 08:06 Uhr

      [...] Einfache Abfragen dieser Art werden intern glaube ich sowieso durch JOIN ersetzt. Also, wieso nicht gleich JOIN einsetzen? Hier habe ich mal einen interessanten Artikel gefunden: JOIN ON vs. WHERE | PHP hates me – Der PHP Blog [...]

    RSS Feed für Kommentare zu diesem Artikel. TrackBack URL

    Hinterlasse einen Kommentar

    Werbung
    PHP Magazin
    Ausgabe 02/2010

    Dieses Mal mit Artikeln zu den Themen OpenSocial und Apache Shindig, Graphentheorie, Smarty3

    t3n
    Ausgabe 19

    Social Media (R)evolution. Weitere Themen sind noSQL, Crowdsourcing ...

    PHP Journal
    Ausgabe 2/2010

    PHP & Windows optimal nutzen, die besten PHP-CMS im Überblick, Google-API mit Zend Framework nutzen.

    Wir wurden schon öfters gefragt, ob man uns nicht irgendwie unterstützen kann. Die Antwort war immer einfach: Klar! Am einfachsten ist es eure nächsten Einkäufe bei Amazon über unsere Link abzuwickeln. Damit würdet ihr uns schon sehr helfen. Über Co-Autoren freuen wir uns aber noch mehr.