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).

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



Deprecated: Directive 'allow_url_include' is deprecated in Unknown on line 0