Prüfen, ob ein bestimmter Datensatz vorhanden ist

Häufig möchte man Prüfen, ob ein bestimmter Datensatz einer MySQL-Tabelle existiert. Dazu hat man mit PHP – wie immer im Leben – mehrere Möglichkeiten. Dieser Beitrag soll die Varianten, die mir in den Sinn gekommen sind, aus Performancesicht untersuchen.

Erstmal kurz die Vorstellung der Kandidaten:

$einlesen = mysql_query("SELECT ID FROM tabelle WHERE ID='123'");
if(mysql_num_rows($einlesen)==1) echo "ja";
else echo "nein";

Bei dieser Variante wird der Primärschlüssel (oder irgendeine andere Spalte) des Datensatzes selektiert. Anschließend wird die Anzahl der zurückgegebenen Datensätze im Result-Set gezählt. Wenn diese 1 beträgt, dann existiert der Datensatz, ansonsten eben nicht. Diese Variante funktioniert nur mit mysql_query(), nicht aber mit mysql_unbuffered_query(). Dazu aber bald mehr in einem Extra-Beitrag über die Unterschiede.

Variante 2 sieht so aus:

$einlesen = mysql_query("SELECT COUNT(*) FROM hotels WHERE ID='123'");
if(mysql_result($einlesen,0)==1) echo "ja";
else echo "nein";

Hier wird das Zählen von MySQL selbst durch die Funktion COUNT() erledigt. Anschließend muss nur noch überprüft werden, ob dieser Wert 1 ist oder nicht. Der Einsatz von mysql_num_rows() ist hier übrigens nicht mehr möglich, da die Anzahl der Zeilen im zurückgegebenen Result-Set immer 1 ist (wenn der Datensatz nicht gefunden wird, ist das Ergebnis 0, aber das ist ja trotzdem 1 Datensatz).

Und die dritte Variante ist wie die zweite, nur dass diesmal nicht mit dem als recht langsam geltenden mysql_result gearbeitet wird, sondern vorher ein mysql_fetch_row() durchgeführt wird, also:

$einlesen = mysql_query("SELECT COUNT(*) FROM hotels WHERE ID='123'");
$einzeln = mysql_fetch_row($einlesen);
if($einzeln[0]==1) echo "ja";
else echo "nein";

mysql_result gilt als langsam, da man sich mit der Funktion frei im Result-Set fortbewegen kann. Normalerweise nutzt man das nicht, weil diese Flexibilität oft unnötig ist und deshalb stattdessen das Result-Set sequentiell ausgelesen wird, was performanter ist. Da es hier aber sowieso nur ein Element ist und das mysql_fetch_row() praktisch eine zusätzliche Funktion ist, habe ich es mit in den Test genommen, um zu überprüfen, ob der Overhead von mysql_result() schwerer wiegt als das zusätzliche Ausführen von mysql_fetch_row().

Übrigens habe ich im Test noch eine for-Schleife drumrum gebastelt, damit wirklich die Performance der Funktionen den Ausschlag gibt und nicht die eine einzelne Anfrage. Wer Genaueres wissen, möchte, kann sich die benutzten Quelltexte herunterladen.

Das Ergebnis sieht nun folgendermaßen aus:

Datei Gesamtlaufzeit durchschnittliche Laufzeit pro Durchlauf Verhältnis zur schnellsten Variante
result_mysql_ num_rows.php 90.760508 s 90.761 ms 100 %
result_mysql_ count.php 93.214035 s 93.214 ms 103 % (+ 3%)
result_mysql_ count_fetch.php 94.726209 s 94.726 ms 104 % (+ 4%)

Die schnellste Variante ist demzufolge das Zählen von PHP erledigen zu lassen – per mysql_num_rows(). Die anderen beiden Varianten haben aber Ihre Vorteile, wenn man nicht nur auf das Vorhandensein eines einzelnen Datensatzes prüft, sondern eine große Datenmenge hat. Dann würde das Zählen von MySQL die von PHP auszuwertende Datenmenge nämlich beträchtlich senken. Aber beim Anwenden von COUNT() auf große Datenmengen immer an meinen früheren Beitrag dazu denken.

Quelltexte und Ergebnisse können wie immer heruntergeladen werden.

Jan hat 152 Beiträge geschrieben

34 Kommentare zu “Prüfen, ob ein bestimmter Datensatz vorhanden ist

  1. Florian sagt:

    Du könntest die dritte Variante, also „result_mysql_count_fetch.php“ noch mit mysql_unbuffered_query statt mysql_query machen. Dies funktioniert da ja wunderbar. Möglicherweise wäre dies die schnellste Lösung, denn mysql_num_rows liegt ja gerade mal 4 Prozent in Front…

    Vielleicht lässt sich dies noch nachträglich testen. Aber auch dieser Vergleich ist schon sehr interessant! Super Arbeit!

  2. admin sagt:

    mysql_unbuffered_query macht hier eigentlich nicht viel Sinn, wenn es um 1 Datensatz geht. Dann gibt es nämlich keinen Unterschied, denn nach diesem einen ist die Pufferung sowieso beendet.

  3. jingles sagt:

    SELECT 1 FROM tabelle WHERE id=’123′

    so mache ich das üblicherweise.
    dann kann man mit mysql_num_rows prüfen, ob der Datensatz existierte.
    Das könnte noch einen kleinen Vorteil bringen, da mysql weder etwas zählen, noch ein Feld auslesen muss.

    Testet das mal jemand?

  4. Banger sagt:

    Sofern der Schlüssel/Datensatz, nach dem man sucht, nicht UNIQUE ist, dürfte man mit jingles‘ Beispiel plus einem angehängten „LIMIT 0,1“ am schnellsten zu einem Ergebnis kommen.

  5. Manuel sagt:

    Interessant zu wissen wäre meiner Meinung nach noch, ob folgendes einen Unterschied macht zu mysql_num_rows():

    $result = mysql_query("SELECT ID FROM 
    tabelle WHERE ID='123'");
    if(mysql_affected_rows($result)==1) echo "ja";
    else echo "nein";

    So mache ich das in der Regel (mehr oder weniger)

  6. freedimension sagt:

    Wurde in der Schleife immer auf ein und denselben Datensatz zugegriffen? Nicht dass hier ein MySQL-internes Caching das Ergebnis verfälscht.

  7. admin sagt:

    @freedimension: Wenn dem so wäre, wäre ja mysql_num_rows nicht die schnellste Variante. Dies wirkt sich ja nur auf das MySQL-interne Zählen aus.

    @Uli: Ca. 20000

  8. Dirk sagt:

    Ich habe mal das Beispiel von jinges getestet und ein Limit angehängt, wie es Banger beschrieben hat. Leider bringt mir aber das angehängte „LIMIT 0,1“ überhaupt keine Ersparnis.

  9. admin sagt:

    Letzten Endes auch nicht ganz unlogisch, denn MySQL ermittelt die 1 ja zuerst für jeden Datensatz und grenzt erst anschließend die Datenmenge ein. Das Laden der 1 anstatt irgendeiner Spalte ist in etwa genauso aufwändig (Konstante schreiben gegen Inhalt eines Zeigers schreiben).

  10. Florian sagt:

    Wenn auf der Spalte(n), die in der WHERE-Klausel steht, auch schon ein Index vom Typ Primary oder Unique ist, kann es nichts bringen.

    Wenn allerdings kein Index gesetzt ist oder einfach nur ein normaler Index, bringt dies schon etwas, da die Suche beendet werden kann, sobald ein Datensatz gefunden wurde. 😉

  11. admin sagt:

    Ist das so? Mir war immer so, als würde zuerst das Gesamtergebnis ermittelt und anschließend die LIMIT-Anzahl an Datensätzen eingeschränkt. Zumindest bei ORDER gehts ja gar nicht anders.
    Muss ich mich doch mal genauer informieren, wei die Reihenfolge ist…

  12. Björn sagt:

    Ich benutze gern folgende Variante, die deutlich macht, dass es um eine Existenz-Prüfung geht, und das olle Array vermeidet:

    $res = mysql_query(‚SELECT EXISTS(SELECT * FROM tabelle WHERE id=123)‘);
    list($exists) = mysql_fetch_row($res);

    Zu LIMIT/ORDER, dass kann frühzeitig abgebrochen werden, falls die Sortierung anhand eines Index durchgeführt wird.

  13. Björn sagt:

    z.B.:
    mysql> EXPLAIN SELECT EXISTS (SELECT * FROM user WHERE Host = ‚localhost‘ AND User = ‚root‘)\G
    *************************** 1. row ***************************
    id: 1
    select_type: PRIMARY
    table: NULL
    type: NULL
    possible_keys: NULL
    key: NULL
    key_len: NULL
    ref: NULL
    rows: NULL
    Extra: No tables used
    *************************** 2. row ***************************
    id: 2
    select_type: SUBQUERY
    table: user
    type: const
    possible_keys: PRIMARY
    key: PRIMARY
    key_len: 228
    ref: const,const
    rows: 1
    Extra: Using index
    2 rows in set (0.00 sec)

    Sieht doch wunderbar aus.

    Die einzigen Subqueries die mir in letzter Zeit untergekommen sind, bei denen MySQL wirklich grobe Schnitzer gemacht hat waren vom Typ „field IN (SELECT …)“ oder „field = (SELECT …)“. Da hat MySQL manchmal nen „dependent subquery“ gebastelt, obwohl der Wert aus dem Subquery eigentlich konstant war. Aber das lässt meist einigermaßen hinkriegen, wenn man stattdessen das Subquery per Join anbindet. Bis 5.0.3x (oder .2x? Ich weiss es nimmer), hat MySQL auch bei GROUP BY Sachen einige Optimierungen einfach weggeschmissen, aber das is ja schon ewig her.

  14. pla sagt:

    Ich vermute „SELECT count(id) from hotels WHERE id = ‚123‘“ ist mit Sicherheit schneller als „SELECT count(*)…“. Wobei id der Primary-Key sein sollte. Das macht sich besonders bei großen Tabellen sehr bemerkbar!

  15. pla sagt:

    @admin: Naja, dort machst du das count ja auch nicht auf den Primary-Key sondern auf irgendwelche Spalten. Aber hier geht es ja darum zu prüfen ob ein Datensatz vorhanden ist, und somit kann man getrost count(id) nutzen.

  16. GhostGambler sagt:

    Das macht sich nicht bemerkbar, wenn überhaupt negativ.
    MySQL muss sich beim * nämlich um gar nichts kümmern, während bei ID je nachdem wie das intern implementiert ist.

  17. Tobias sagt:

    Ich Teste es mit der 1. Variante jedoch nutz ich dann $a = mysql_fetch_assoc($result);
    if($a !== false) { echo „vorhanden“; }
    könnte das mal jemand Testen? wenn der Beitrag noch nicht zu alt ist ^^‘

  18. admin sagt:

    Leider fehlt mir grad die Zeit das zu testen, aber hier kurz noch einige Gedanken, wie man das noch verbessern könnte, Tobias:
    1. Zwischenspeicherung in $a kostet -> könnte man auch direkt in das if schreiben
    2. if($a !== false) sollte schneller durch if(!$a) gehen. Die Typüberprüfung durch das zweite Gleichheitszeichen ist sowieso nicht notwendig, oder?

  19. Tobias sagt:

    Stimmt mit Variable ist mir auch gestern nach erstellen eingefallen ^^.
    2. naja wenn php zuerst den Typ prüft dürfte es sich ja da schon entscheiden ob es ein array oder ein bool ist.
    Aber so genau weiß ich das jetzt auch nicht.

  20. Chris sagt:

    Ich persönlich würde bei der Variante „if($einzeln[0]==1) echo „ja“;“ noch eine Prüfung mittels isset() machen. Denn strict ist das ganze nicht, wenn $einzeln[0] nicht gegeben ist, nicht 😉

  21. GhostGambler sagt:

    Doch, $einzeln[0] ist immer gesetzt.
    Es fehlt ggf. eine Fehlerbehandlung, ob der SQL-Server ein Problem hatte / der Query falsch war. Der Code darunter ist aber vollkommen okay.

  22. Fidelis sagt:

    Hat gut funktioniert, danke!
    Jetzt kann ich gezielt Fehlermeldungen ausgeben wenn ein bestimmter Text in der Datenbank noch nicht vorhanden ist!
    Gruß

  23. Martin sagt:

    Vielen Dank, dein Post beantwortet mir eine Frage die ich mir nie gestellt habe.
    Habs aber bei einem Projekt mit gut 50.000 Datensätzen getestet und die result_mysql_ num_rows.php Variante hat tatsächliche einen kleinen Geschwindigkeitesvorteil gebracht.

  24. Micha93 sagt:

    An für sich sind die Codes nicht schlecht. Allerdings empfehle ich aus ==1 ein >=1 zu machen. Denn so geht man nämmlich nicht nur sicher, dass ein Eintrag schon bereits vorhanden ist, sondern, dass er mehrfach schon nicht vorhanden ist. Sprich er könnte ja auch schon öfters als einmal vorhanden sein.
    Ist allgemeingültiger und manchmal (wie z.B. bei mir aktuell), ist es sicherer, dies auch so zu formulieren

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>