MySQL unter PHP debuggen mit einer eigenen MySQL Klasse

Bei einem meiner Projekte kam es zu ungewöhnlich langen Ladezeiten eines Formulars. Da hier allerdings auch sehr viele Datenbankabfragen ausgeführt werden müssen und dabei auch noch die Werte per COUNT() gezählt werden wunderten mich die Zeiten nicht wirklich.

Nun wollte ich natürlich wissen, welche Abfrage dabei besonders viel Zeit in Anspruch nimmt um dann gezielt optimieren zu können. Ich setze in dem Projekt auf die MySQLi Klasse von PHP. Da ich hierbei die Klasse objektorientiert nutze, war es sehr einfach möglich diese zu erweiterten. Eine Möglichkeit wäre es natürlich gewesen eine eigene Funktion zu schreiben, aber in diesem Fall hätte ich auch alle Skript anpassen müssen, in denen ich eine Query ausführe. Daher habe ich mich dazu entschlossen einfach die query() Funktion von MySQLi zu überschreiben. Hier ein stark vereinfachtes Beispiel, wie so etwas aussehen könnte:

<?php

class mysqliDebugger extends mysqli {
	
	public $queries = array();
	public $queries_time = 0;
	
	function __construct($host, $user, $pass, $name, $port, $sock){
		parent::__construct($host, $user, $pass, $name, $port, $sock);
	}
	
	function __destruct(){
		parent::__destruct();
	}
	
	function query($query){
		$start = microtime(true);
		
		if(!$result = parent::query($query)){
				$error_message = $this->error;
		}
		
		$end = microtime(true);
		
		$this->queries[] = array(
			'number' => count($this->queries),
			'query' => $query,
			'error' => $error_message,
			'time' => $end - $start
		);
		
		$this->queries_time += $end - $start;

		return $result;
	}
}

$mysqli = new mysqliDebugger('host', 'user', 'pass', 'name');
if($result = $mysqli->query('SELECT * FROM tablename')){
	while($row = $result->fetch_assoc()){
		// Verarbeiten der Ergebniszeilen
	}
}


if($result = $mysqli->query('SELECT * FROM tablename2')){
	while($row = $result->fetch_assoc()){
		// Verarbeiten der Ergebniszeilen
	}
}

?>

Sehen wir uns die wichtigsten Zeilen des Quellcodes an. Die Zeilen 8-10 zeigen den Konstruktor unserer neuen Klasse, die den Konstruktor der geerbten Klasse MySQLi aufruft. In den Zeilen 16-36 überschreiben wir die query() Funktion der MySQLi Klasse mit unserer Debugging-Klasse. Dort wird zuerst einmal zur Protokollierung der Ausführungszeit der Startzeitpunkt mit Hilfe der microtime() Funktion ermittelt. Anschließend wird der Query mit dem Aufruf parent::query($query) an die query() Funktion der MySQLi Klasse übergeben. Einen eventuell auftretenden Fehler speichern wir dabei in einer Variablen ab.

In Zeile 23 speichern wir die Endzeit des Query. Anschließend speichern wir alle Werte, die wir zur späteren Auswertung benötigen in einem Array gespeichert. Zusätzlich summieren wir noch alle Zeiten auf, um später die Gesamtdauer aller Statements bestimmen zu können. Die ab Zeile 38 wird eine einfache Instanziierung und Nutzung der Klasse aufgezeigt, wie man sie auch von MySQLi kennt. Wer also in seinen Skripten schon MySQLi verwendet kann einfach beim Instanziieren der Klasse die neue Debugging-Klasse verwenden und muss keine weiteren Zeilen im Quellcode ändern. Ich empfehle eine kleine Bedingung zu verwenden, die feststellt, ob der Debugging-Modus aktiv ist und nur dann die Debugging-Klasse zu verwenden, wenn sie auch benötigt wird. Das spart im produktiven Einsatz etwas Rechenzeit. Eine mögliche Bedingung könnte wie folgt aussehen:

if($debug){
	$mysqli = new mysqliDebugger('host', 'user', 'pass', 'name');
} else {
	$mysqli = new mysqli('host', 'user', 'pass', 'name');
}

Anstelle der $debug Variablen könntet ihr auch einen GET Parameter oder einen Session Parameter verwenden. Ich rate aber dringend davon ab einen einfach zu erratenden GET Parameter zu verwenden, da in solchen Fällen eure SQL-Statements für Unbefugte sichtbar werden könnten, was unter Umständen die Sicherheit eueres Systems gefährden könnte.

Nachdem wir die Daten gesammelt haben möchten wir sie natürlich auch ausgeben. Das solltet ihr auch wieder die vorherige Bedingung verwenden. Dazu erstellen wir einfach eine Tabelle, die die gesammelten Daten enthält:

<table style="border: 1px solid #000;" rules="all">
	<caption>Gesamtdauer: <?= $mysqli->queries_time ?></caption>
	<tr>
		<th>Anzahl</th>
		<th>Query</th>
		<th>Fehler</th>
		<th>Zeit</th>
	</tr>
<? foreach($mysqli->queries as $query) : ?>
	<tr>
		<td><?= $query['number'] ?></td>
		<td><?= $query['query'] ?></td>
		<td><?= $query['error'] ?></td>
		<td><?= $query['time'] ?></td>
	</tr>
<? endforeach ?>
</table>

Nehmen wir an, wir die Tabelle aus dem ersten Statement ist in der Datenbank nicht vorhanden. In diesem Fall würden wir einen SQL-Fehler erhalten. In der Tabelle können wir dann direkt den Fehler ermitteln und entsprechend das Statement korrigieren. Auch hier wird das Statement gespeichert und die Zeit gemessen.

Gesamtdauer: 0.0025820732116699
Anzahl Query Fehler Zeit
0 SELECT * FROM tablename Table ’name.tablename‘ doesn’t exist 0.00076198577880859
1 SELECT * FROM tablename2 0.0018200874328613

Ihr habt mit dieser kleinen Beispiel-Klasse einen sehr einfachen Debugger zur Hand, mit dem ihr sehr schnell die langsamen Statements in eurem System finden könnt. Ihr könnt die Klasse natürlich auch noch nach euren Wünschen erweitern und auch andere Funktionen von MySQLi überschreiben. Wenn ihr, so wie ich, zentral an einer Stelle die Datenbankverbindung erzeugt könnt ihr durch die Bedingung aus dem zweiten Quellcode eure gesamte Applikation debuggen.

Ich habe damit in meinem Projekt schon zwei überflüssige Statements finden können und einige andere Statements noch weiter optimieren können. Ich hoffe, dass euch der Debugger auch bei eurer täglichen Arbeit unterstützen kann. Über Anregungen und Kommentare Wrede ich mich wie immer freuen.

Veröffentlicht von

Bernhard ist fest angestellter Webentwickler, entwickelt in seiner Freizeit Plugins, schreibt in seinem Blog über WordPress und andere Themen, treibt sich gerne bei den WP Meetups in Berlin und Potsdam herum und läuft nach Feierabend den ein oder anderen Halbmarathon.

1 Kommentar » Schreibe einen Kommentar

  1. […] Sehr häufig wird gefordert ein assoziatives Array als HTML Tabelle auszugeben. Ich nutze hier sehr oft die implode() Funktion, um dabei mit möglichst wenig Quellcode zu dem Gewünschten Ergebnis zu kommen. Da ich meine Lösung wirklich sehr schön und genial einfach finde, wollte ich euch kurz die Funktion präsentieren. Als auszugebendes Array nehmen wir das MySQL-Debugging Array aus meinem vorherigen Artikel MySQL unter PHP debuggen mit einer eigenen MySQL Klasse. […]

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert