IPC 2013 Spring

Einen Datensatz aus einem Result-Set laden

Heute gibt es mal wieder einen Beitrag zur Performance im Umgang von PHP und MySQL – angeregt duch eine Mail von Alex Kuhrt. Eigentlich wollte ich dieses Thema schon lange mal untersuchen, aber bin leider nicht dazu gekommen. Eine der häufigsten (wenn nicht die häufigste) Anwendungen beim Umgang mit MySQL ist das Weiterverarbeiten der Daten eines Result-Sets. Dafür bietet PHP jede Menge Möglichkeiten: mysql_fetch_object, mysql_fetch_array, mysql_fetch_assoc und mysql_fetch_row. Eventuell gibt es auch noch mehr, weiß ich jetzt nicht. Jedenfalls ist es schon verwunderlich, dass PHP da so viele Funktionen anbietet. Ich wollte der Sache mal auf den Grund gehen und die verschiedenen Varianten hinsichtlich ihrer Performance untersuchen.

Die Gemeinsamkeit aller Varianten ist, dass Sie aus einem Result-Set einen einzelnen Datensatz laden, je nachdem, auf welchen Datensatz der interne zeiger gerade zeigt. Grundlegender Unterschied ist, dass die Spalten der Datensätze unterschiedlich angesprochen werden. Bei mysql_fetch_object wird beispielsweise ein Objekt zurückgegeben, über das dann mittels -> auf eine bestimmte Spalte zugegriffen werden kann. mysql_fetch_assoc gibt ein assoziatives Array zurück. Man kann also über ['spaltenname'] auf die jeweiligen Spalten des Datensatzes zugreifen. mysql_fetch_row gibt ein normales Array mit numerischen Indizes zurück. Die Indizes sind in der Reihenfolge, wie sie in der Anfrage-Query angegeben wurden. Der Zugrif erfolgt dann beispielsweise über [0]. Und mysql_fetch_array vereint das alles, indem es als zweiten Parameter mit einem Flag die Art der Rückgabe bestimmt.

Die einzelnen Zugriffsarten sollten doch aber nicht das einzigste sein, was die Entscheidung, welche Funktion man nimmt, beeinflusst, denn eine Umstellung fällt nicht wirklich schwer. Wollen wir uns deshalb einmal die Performance ansehen.
Dazu dient ein Script, das folgendermaßen aussieht:

$einlesen = mysql_query("SELECT ID,adresse,geobreite,geolaenge FROM hotels");
while($item = mysql_fetch_*($einlesen)) {
  echo $item['ID'] // oder $item->ID oder $item[0]
}

Als Datengrundlage wurde eine Tabelle mit rund 20000 Datensätzen benutzt, was hier aber unerheblich ist, da es ja um die Performance der PHP-Funktionen geht.

Hier also nun die mit Spannung erwartete Tabelle:

Datei Gesamtlaufzeit durchschnittliche Laufzeit pro Durchlauf Verhältnis zur schnellsten Variante
mysql_fetch_row.php 310.897048 s 310.897 ms 100 %
mysql_fetch_assoc.php 319.98842 s 319.099 ms 103 % (+ 3%)
mysql_fetch_array.php 328.532407 s 328.532 ms 106 % (+ 6%)
mysql_fetch_object.php 376.40720 s 376.041 ms 121 % (+21%)

mysql_fetch_row ist also die schnellste Veriante, dicht gefolgt von mysql_fetch_assoc. Dahinter reihen sich mysql_fetch_array sowie weit abgeschlagen mysql_fetch_object ein.
Nun zu den Erklärungsversuchen. mysql_fetch_row gibt ein Array mit numerischen Indizes zurück. Dies ist im Vergleich zu mysql_fetch_assoc schneller, da die assoziativen Indizes nach den Spaltennamen nicht angelegt werden müssen. Trotzdem habe ich die assoc-Variante grün markiert, weil sie gegenüber dem numerischen Index viel verständlicher ist, wenn man mal über den Code drüberguckt. Außerdem passieren mit mysql_fetch_row viele Fehler, wenn man mal einfach eine Spalte in die Query hinzufügt (und dies nicht am Ende der Spaltenliste tut). Anschließend kommt mysql_fetch_array, das in seiner Standardversion das Flag MYSQL_BOTH nutzt und somit ein Array zurückgibt, das sowohl numerische als auch assoziative Indizes hat. Das Array ist also doppelt so groß wie bei den zuvor genannten Varianten – das ist Verschwendung und gehört natürlich bestraft. Nutzt man mysql_fetch_array mit dem Flag MYSQL_ASSOC, kommt es auf einen ähnlichen Wert wie mysql_fetch_assoc, aber dann kann man auch gleich diese Funktion nehmen.
Und ganz abgeschlagen kommt dann noch mysql_fetch_object. Wie so oft im Programmiererleben geht Objektorientierung auf Kosten der Performance. Da ich immer die Notwendigkeit von OOP anhand des daraus folgenden Nutzens festmache, würde ich bei der hier besprochenen Anwendung darauf verzichten, da es eigentlich keinen Mehrwert bietet.

Als Fazit empfehle ich die Nutzung von mysql_fetch_assoc, einfach weil diese Variante wenig fehleranfällig (gegenüber mysql_fetch_row) ist und dazu noch recht performant. Wem es auf die reine Performance ankommt und weiß, was er tut, der kann auch mysql_fetch_row nutzen (bzw. mysql_fetch_array mit dem Flag MYSQL_NUM).

Zum Schluss gibts noch die Ergebnisse und die Quelltexte.

PS: Ich hoffe dieser Beitrag genügt den grammatikalischen und ortographischen Ansprüchen einiger Leser ;-)


Schlagwörter: , , ,

40 Kommentare bisher »

  1. kb sagt

    am 3. Februar 2008 @ 15:31

    Schade. Aber dachte ich mir schon. Nur ist es immer so umständlich Arrays zu schreiben, man muss die rechte Hand ziemlich verdrehen um alt-gr und dann 8 / 9 drücken. Da sind die Pfeile von den Objekten einfach schneller geschrieben.
    Gibts da auch schon eine Lösung dafür?
    Evtl. könnte man ja mit list() aus den Arrays Variablennamen machen .. ist aber auch nicht das Optimum finde ich.

  2. Kay Butter sagt

    am 3. Februar 2008 @ 15:35

    Der nutzen von Fetch-Object steigt dann, wenn man der Funktion eine Klasse übergibt, die das Datensatz Objekt dann haben sollen. Ganz sinnlos ist die Funktion nicht ;)

    Wird das eigentlich langsamer, wenn man eigene Klassen verwendet, bei mysql_fetch_object?

  3. admin sagt

    am 3. Februar 2008 @ 15:35

    @kb: Natürlich kann man das Objekt in ein Array überführen, nur wird der Performance-Verlust dadurch nur noch schlimmer. Ich finde, sooo schwer sind die eckigen Klammern nicht zu treffen.

    @Kay: Ob man nun der verarbeitenden Funktion das Objekt oder das Array übergibt, wär mir eignetlich völlig egal – oder hab ich Dich falsch verstanden?

  4. Kay Butter sagt

    am 3. Februar 2008 @ 15:49

    Ähm. Man kann der fetch_object funktion einen Klassen Namen geben, um den Objekten eigene Methoden mitzugeben etc. Zum Beispiel kann man dann den Objekten Getter und Setter Methoden geben oder eine Save methode um das Ding nach dem Bearbeiten des Datensatzes einfach wieder speichern zu können.

  5. admin sagt

    am 3. Februar 2008 @ 15:51

    Ah ok, also doch was falsch verstanden. Gut, das kann man machen, wenn das ganze Projekt auf OOP basiert – obs nötig ist, ist ne andere Geschichte.

  6. Matt sagt

    am 3. Februar 2008 @ 16:40

    Es gibt eine einfache Lösung für alle, die Probleme mit den eckigen Klammern haben:
    Mac kaufen. Da liegen diese auf Alt+5 bzw Alt+6 :D

  7. Kay Butter sagt

    am 3. Februar 2008 @ 16:49

    @Matt: ja, ist leider nicht beschriftet… und ich tippe regelmäßig daneben, wenn ichs ne weile nicht benutzt hab

  8. Matt sagt

    am 3. Februar 2008 @ 16:53

    Ja, die eher magere Beschriftung der Apple-Tastatur ist gerade für Umsteiger ein Problem. Aber dagegen hilft ja die Tastaturübersicht (oder mein Cheatsheet).

  9. Kuhrti Brothers » Blog Archive » Schneller Performanc mit mysql_fetch_asso() sagt

    am 3. Februar 2008 @ 17:07

    [...] Hier geht es zum kompletten Test auf phpperformance.de… [...]

  10. Alex Kuhrt sagt

    am 3. Februar 2008 @ 17:14

    Ich danke dir viel mals für diesen Test!

  11. kb sagt

    am 3. Februar 2008 @ 17:29

    Ich meinte nicht das Objekt in ein Array zu überführen, sondern eher umgekehrt. Mit dem Array etwas praktikableres anzustellen ;) .

  12. GhostGambler sagt

    am 3. Februar 2008 @ 18:00

    Jop, besser :)

    buffered vs. unbuffered wäre vielleicht auch noch ein praktikables Thema

  13. admin sagt

    am 3. Februar 2008 @ 18:22

    Oho, das war ja fast eine 1 mit Sternchen vom Deutsch-Lehrer ;-)

    buffered vs. unbuffered meinst Du mysql_query() vs. mysql_unbuffered_query() – oder? Hab ich aufm Plan.

  14. felix sagt

    am 3. Februar 2008 @ 19:11

    Interessanter Test – Danke! :)

  15. GhostGambler sagt

    am 3. Februar 2008 @ 23:05

    Bin nur leider kein Deutsch-Lehrer und abgesehen von Grammatik und Rechtschreibung in Deutsch auch nicht unbedingt mit Talent gesegnet :)

  16. Florian sagt

    am 3. Februar 2008 @ 23:07

    Wo wir da gerade beim Thema mysql_buffered_query sind. Da würde mich interessieren, wie man am Schnellsten einen numerischen Wert auslesen kann:

    Also entweder mit mysql_unbuffered_query und dann Daten per mysql_fetch_row holen oder per mysql_query und mysql_result. Bei mysql_result lässt sich nämlich mysql_unbuffered_query nicht nutzen. ;)

    Super Artikel! Das Ergebnis hatte ich zwar nicht anders erwartet, aber schön, dass sowas nachgemessen wird. Weiter so!

  17. Christian sagt

    am 3. Februar 2008 @ 23:08

    Schöner Test! _row ist zwar schön schnell, aber ich muss dir recht geben, dass _assoc viel schöner anzusehen ist… naja wer aber wirklich die performance braucht, wird auch die _row nachteile in kauf nehmen.

    vorallem wenn man dadurch serverkosten einsparen kann! :)

  18. GhostGambler sagt

    am 4. Februar 2008 @ 01:00

    Wartungskosten von Software sind höher als Serverkosten.

    Das OOP-Paradigma hat seine Beliebtheit ja auch nicht wegen seiner mäßigen Performance ergattert~
    Wenn schneller immer besser wäre, egal zu welchen anderen Nachteilen, wäre der Blog hier wohl unter assemblerperformance.de zu finden :D

  19. Ralf sagt

    am 4. Februar 2008 @ 08:00

    @kb (#1): Entweder du benutzt einen vernünftigen Editor mit Codevervollständigung, dann sparst du dir zumindest schon mal AltGr+9 (bzw. AltGr+0 = }). Oder du benutzt eine amerikanische Tastaturbelegung. Dort sind die Eckigen Klammern über die Shift-Taste zu erreichen. Die deutschen Umlaute sind bei der Programmierung sowieso nicht wirklich von Nöten, von daher kannst du zumindest beim Coden weitgehend darauf verzichten.

  20. admin sagt

    am 4. Februar 2008 @ 08:05

    @Florian: Wir sind noch gar nicht beim Thema mysql_query vs. mysql_unbuffered_query. Kommt in einem der nächsten Beiträge…

  21. Jo sagt

    am 4. Februar 2008 @ 09:14

    Vielen Dank, das interessierte mich schon immer mal.
    Dann mach ich also mal ein Suchen/Ersetzen von fetch_array nach fetch_assoc über meine Projekte ;)

    Das mit mysql_result() würde mich auch Wunder nehmen. Vor allem wenn man nur einen Wert benötigt (z.B. COUNT(*) AS total), was ist schneller? Das ganze Ergebnis fetchen und den Wert auslesen oder direkt auf "total" zugreifen über mysql_result?

    Gerne erwarte ich einen entsprechenden Beitrag ;)

  22. admin sagt

    am 4. Februar 2008 @ 09:18

    @Jo: Der Bericht über das Auswerten eines einzelnen Datensatzes (mit 1 Spalte) ist schon geschrieben, muss nur noch ein wenig überarbeitet werden. Kommt bald.

  23. PHP Blogger: Performance: MySQL Result-Sets auslesen - Ein PHP Blog auf deutsch sagt

    am 4. Februar 2008 @ 10:13

    [...] interessanter Artikel vom PHP Performance Blog. Diesmal wurde untersucht, mit welcher Methode man am schnellsten Daten aus einem MySQL Result-Set laden [...]

  24. loci sagt

    am 4. Februar 2008 @ 11:02

    @Matt: einfach ein us-layout nutzen. da liegen die klammern viel sinnvoller.

  25. therouv.de » Geschwindigkeit von MySQL-Abfragen sagt

    am 4. Februar 2008 @ 22:05

    [...] selbst lesen: http://phpperformance.de/einen-datensatz-aus-einem-result-set-laden/ mysql, performance, php [...]

  26. Schlagstock sagt

    am 5. Februar 2008 @ 14:29

    Der Test bringt nicht sonderlich viel. Das würde ja bedeuten, das ich meine Queries mit SQL baue und dann nativ abfeuern würde. Das bringt vielleicht dem Riffy ausm Clan was, aber Leute die größtenteils ihren Kram mappen ist das uninteressant. Hilfreicher wäre ein Performancevergleich zwischen Db und MDB2 oder Zend_Db_Table und Doctrine.

  27. admin sagt

    am 5. Februar 2008 @ 15:02

    Verstehe nicht wirklich, was Du sagst, Schlagstock. Wieso bezieht sich der Test auf das native Abfeuern? Wer oder was Riffy sein soll, ist mir auch nicht ganz klar.

  28. Schlagstock sagt

    am 5. Februar 2008 @ 15:53

    Wer spricht den heute noch seine DB direkt an? Kiddies höchstens, die ihre Clan Seite zusammenfrickeln. Aber ein MySQL Test hilft mir nicht weiter wenn ich abstrahiere und mit andern dbms arbeite. Man könnte hier höchstens noch ein MySQL Test raushauen im Vergleich zu mysql_query, PDO, MySQLi , Adodb, db, mdb2 und zend_db. Wäre interessant zu wissen mit welcher Technik ein vorgegebens Ergebnis am schnellsten laden lässt.

  29. tcomic sagt

    am 5. Februar 2008 @ 16:22

    @ Schlagstock: Auch ein DB-Abstraction-Layer muss irgendwie geschrieben werden und dazu sind diese Informationen und Tests durchaus interessant. Das hier ist ja keine Diskussion ob man die DBs aus seine Apps direkt ansprechen soll oder nicht…
    Oder habe ich den Sinn deiner Posts faslch verstanden?

  30. Adrian sagt

    am 6. Februar 2008 @ 09:08

    Vielen Dank für diesen Test! Weiter so!

  31. Vordefinierte Wrter ersetzen (aus Array bzw. aus DB auslesen) - PHP @ tutorials.de: Forum, Tutorial, Anleitung, Schulung & Hilfe sagt

    am 7. Februar 2008 @ 18:58

    [...] du meist nur den assoziativen Array benutzt. Hier gibt es einen aktuellen Artikel darüber: Einen Datensatz aus einem Result-Set laden __________________ Buchempfehlung: Tobias Schlitt & Kore Nordmann – eZ Components Blog: [...]

  32. Wochenendbeilage #17 | REDUXO sagt

    am 15. Februar 2008 @ 18:01

    [...] PHP Performance: Einen Datensatz aus einem Result-Set laden [...]

  33. Tobias sagt

    am 16. März 2008 @ 19:11

    @Kay Butter (2. Beitrag)
    Ich wollte dazu mal meine Erfahrung mit einflißen lassen.
    Ich selbst programimere bei großen Projekten nur noch mit php5 und stark oop, und hab auch schon mysql_fetch_object genutzt.

    Bsp.:

    mysql.class.php
    public function getObj ($class)
    { return mysql_fetch_object($this->result, $class); }

    test.class.php
    public function load($testID)
    { … $testObj = mysql->getObj(__CLASS__); … }

    $testObj, würde jetzt ein Obekt von test sein, mit allen Eigenschaften.

    Wichtig hierbei ist das alle Spalten in der DB::Tabelle auch als Eigenschaft in der Klasse sind.

    So kann man ein leichtes laden von Objekten realisieren, aber wie schon im Beitrag, es war zu langsam, so hab ich das wieder rückgängig gemacht und nutze mysql_fetch_assoc(); :)

    Interresant ist auch das speichern ganzer Objekte in der datenbank mit serialize(); (Spalten Typ: blob)

    So das war von mir mal ein kleiner einwurf.

  34. Marco sagt

    am 24. März 2008 @ 15:06

    Bei mir ist "mysql_fetch_assoc" nicht immer schneller als "mysql_fetch_object". Außerdem treten bei "mysql_fetch_assoc" größere Schwankungen auf, sodass es manchmal sehr schnell ist, manchmal aber auch viel langsamer als die anderen Varianten.
    Ist das normal? Ich habe auch solche Abfragen wie im Beispiel oben.

  35. Calvados sagt

    am 13. April 2008 @ 17:57

    Naja, ganz so unerwartet waren die Ergebnisse für mich nicht ;)
    Wurde bereits 2002 bei php.net getestet:

    Benchmark on a table with 38567 rows:

    mysql_fetch_array
    MYSQL_BOTH: 6.01940000057 secs
    MYSQL_NUM: 3.22173595428 secs
    MYSQL_ASSOC: 3.92950594425 secs

    mysql_fetch_row: 2.35096800327 secs
    mysql_fetch_assoc: 2.92349803448 secs

  36. Forum schneller machen ? - PHP @ tutorials.de: Forum, Tutorial, Anleitung, Schulung & Hilfe sagt

    am 24. Juni 2008 @ 21:30

    [...] eine Klasse verarbeitet die Daten weiter, ist die Benutzung eines Objekts natürlich sinnvoller. Einen Datensatz aus einem Result Set laden Bitte auch die Kommentare lesen __________________ Last we checked PHP IS a [...]

  37. Phlegma sagt

    am 31. Juli 2008 @ 15:58

    Hi und danke für den Test!
    Mich würde noch interessieren wie sich die MySQLi Methoden (besonders die prepared und multi_query) im Geschwindigkeitstest im Vergleich schlagen…

    gruß, Ph

  38. Tobi sagt

    am 5. Juni 2009 @ 18:22

    Danke für deinen Post! Ich arbeite seit anfang an mit mysql_fetch_object, da ich es in einem Tutorial so gelernt habe… Jetzt bin ich an einem größeren Script bei dem es auch auf Performance ankommt und sehe, dass mysql_fetch_object mal gar nicht geht ^^

    Dankeschön für den Test!

  39. David sagt

    am 9. August 2011 @ 14:22

    while(list($id, $adresse, $geoBreite, $geoLaenge) = mysql_fetch_row($einlesen))

    So kann man Variablennamen benutzen (die in einem guten Editor auch automatisch vervollständigt werden) und behält die Übersicht. Ändert sich was im Query muss nur list() angepasst werden.

    -> http://de2.php.net/list#example-4039

  40. Michael Sattler sagt

    am 6. Januar 2013 @ 19:22

    Bezüglich mysql_result() haben meine Messungen folgendes ergeben:

    Bei Abfragen, die nur eine Zeile enthalten (was nicht selten vorkommt), ist mysql_result ca. 20-30% schneller als mysql_fetch_row!

    Vorausgesetzt, es gibt nur eine Spalte:
    $feld = mysql_result($abfrage,0)

    oder (bei mehreren Spalten) einen numerischen Offset:
    $feld = mysql_result($abfrage,0,5)

    Dagegen macht die Angabe eines Spaltennamens den Vorteil wieder zunichte:
    $feld = mysql_result($abfrage,0,'spalte')

    Da mysql_result im Vergleich zu mysql_fetch_* zudem eine PHP-Zeile einspart, verwende ich es immer bei Abfragen von Einzelwerten (eine Spalte in einer Zeile).

Komentar RSS · TrackBack URI

Hinterlasse einen Kommentar

Name: (erforderlich)

eMail: (erforderlich)

Website:

Kommentar: