W3docs

PHP MySQL Prepared Statements: Ein umfassender Leitfaden

Prepared Statements steigern Sicherheit und Effizienz von PHP-Anwendungen mit Datenbankzugriff. Dieser Leitfaden erklärt MySQLi und PDO im Detail.

Prepared Statements sind die wichtigste Technik für das Schreiben von sicherem PHP-Code, der mit einer Datenbank kommuniziert. Sie trennen den SQL-Befehl von den Daten, auf die er zugreift – das verhindert SQL-Injection zuverlässig und beschleunigt außerdem Abfragen, die wiederholt ausgeführt werden. Dieser Leitfaden erklärt, was Prepared Statements sind, warum sie wichtig sind und wie man sie sowohl mit der MySQLi- als auch mit der PDO-Erweiterung einsetzt.

Diese Seite behandelt:

  • Was ein Prepared Statement ist und warum das Modell „einmal kompilieren, viele Male ausführen" existiert
  • Vorbereitung von INSERT- und SELECT-Abfragen mit MySQLi
  • Dieselben Muster mit PDO (benannte Platzhalter)
  • Häufige Fallstricke: Fehlerreporting, falscher Bindungstyp und Wiederverwendung von Statements

Was sind Prepared Statements?

Ein Prepared Statement ist eine SQL-Abfrage, die in zwei Stufen an die Datenbank gesendet wird:

  1. Prepare — Sie senden das SQL mit ?-(oder :name-) Platzhaltern anstelle echter Werte. Die Datenbank parst, kompiliert und optimiert diese Vorlage einmalig.
  2. Execute — Sie senden die eigentlichen Werte separat. Die Datenbank fügt sie in den bereits kompilierten Plan ein und führt ihn aus.

Da die Werte über einen anderen Kanal als der SQL-Text übertragen werden, verwechselt die Datenbank Daten niemals mit Befehlen. Ein Wert wie ' OR '1'='1 wird als literaler Suchstring behandelt, nicht als auszuführendes SQL – genau deshalb scheitern Injection-Angriffe an Prepared Statements.

Warum Prepared Statements verwenden?

  • Sicherheit. Benutzereingaben können die Struktur einer Abfrage niemals verändern. Das ist der empfohlene Schutz gegen SQL-Injection und der Grund, warum Abfragen niemals durch Verkettung von Variablen zu einem String gebaut werden sollten.
  • Performance. Die Abfrage wird einmal geparst und kompiliert. Wenn Sie sie viele Male ausführen (z. B. 1.000 Zeilen in einer Schleife einfügen), verwendet die Datenbank denselben Plan wieder, anstatt ihn jedes Mal neu zu parsen.
  • Saubererer Code. Platzhalter bedeuten kein manuelles Escaping mit mysqli_real_escape_string() und kein Jonglieren mit Anführungszeichen. Sie binden eine Variable – fertig.

Faustregel: Sobald ein Teil einer Abfrage aus Benutzereingaben stammt – ein Formularfeld, ein URL-Parameter, ein Cookie – verwenden Sie ein Prepared Statement.

Die Schritte

Jedes Prepared Statement folgt demselben Lebenszyklus:

  1. Verbindung zur Datenbank herstellen.
  2. Das SQL mit Platzhaltern vorbereiten.
  3. Variablen an die Platzhalter binden.
  4. Das Statement ausführen.
  5. Ergebnisse abrufen (bei SELECT-Abfragen).
  6. Das Statement schließen.

Prepared INSERT mit MySQLi

MySQLi verwendet positionelle ?-Platzhalter. Sie werden mit mysqli_stmt_bind_param() gebunden, wobei das erste Argument ein Typstring ist: s für string, i für integer, d für double/float, b für blob.

<?php

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); // throw on errors

$conn = mysqli_connect("localhost", "username", "password", "database");

$stmt = mysqli_prepare($conn, "INSERT INTO users (name, email) VALUES (?, ?)");

// "ss" => both placeholders are strings, in order
mysqli_stmt_bind_param($stmt, "ss", $name, $email);

$name  = "John";
$email = "[email protected]";
mysqli_stmt_execute($stmt);   // inserts John

$name  = "Jane";
$email = "[email protected]";
mysqli_stmt_execute($stmt);   // reuses the same compiled statement, inserts Jane

mysqli_stmt_close($stmt);
mysqli_close($conn);

bind_param bindet per Referenz, sodass Sie $name/$email ändern und execute() erneut aufrufen können, ohne neu zu binden – die neuen Werte werden automatisch übernommen. Das ist der Vorteil von „einmal kompilieren, viele Male ausführen" in der Praxis.

Prepared SELECT mit MySQLi

Bei einem SELECT führen Sie das Statement aus und lesen anschließend Zeilen aus. Die sauberste Methode ist mysqli_stmt_get_result(), die ein normales Ergebnisset zurückgibt, über das Sie iterieren können:

<?php

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

$conn = mysqli_connect("localhost", "username", "password", "database");

$stmt = mysqli_prepare($conn, "SELECT id, name FROM users WHERE email = ?");
mysqli_stmt_bind_param($stmt, "s", $email);

$email = "[email protected]";
mysqli_stmt_execute($stmt);

$result = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($result)) {
    echo $row["id"] . ": " . $row["name"] . "\n";
}

mysqli_stmt_close($stmt);
mysqli_close($conn);

Prepared Statements mit PDO

PDO ist die andere verbreitete Datenbankerweiterung. Viele Entwickler bevorzugen sie, weil sie über verschiedene Datenbanksysteme hinweg funktioniert und benannte Platzhalter (:email) unterstützt, die leichter zu lesen sind.

<?php

$pdo = new PDO(
    "mysql:host=localhost;dbname=database;charset=utf8mb4",
    "username",
    "password",
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

// INSERT with named placeholders
$stmt = $pdo->prepare("INSERT INTO users (name, email) VALUES (:name, :email)");
$stmt->execute([":name" => "John", ":email" => "[email protected]"]);

// SELECT and fetch
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE email = :email");
$stmt->execute([":email" => "[email protected]"]);

foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
    echo $row["id"] . ": " . $row["name"] . "\n";
}

Beachten Sie, dass bei PDO keine Typen deklariert werden – Sie übergeben ein assoziatives Array mit Werten direkt an execute(). Das Setzen von PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION bewirkt, dass PDO bei einem Fehler eine Ausnahme wirft, sodass Probleme niemals stillschweigend ignoriert werden.

Häufige Fehler

  • Fehlerreporting vergessen. Standardmäßig kann MySQLi still fehlschlagen. Rufen Sie mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) auf (oder verwenden Sie PDO-Exceptions), damit eine fehlerhafte Abfrage eine Ausnahme wirft, anstatt false zurückzugeben.
  • Falschen Typanzahl binden. Der Typstring in bind_param muss genau ein Zeichen pro ? enthalten. "ss" für zwei Platzhalter, "si" für einen string und danach einen integer.
  • Platzhalter dort einsetzen, wo SQL keinen erlaubt. Sie können Werte binden, keine Bezeichner. WHERE id = ? funktioniert; ORDER BY ? oder SELECT * FROM ? nicht – Tabellen- und Spaltennamen müssen fest kodiert oder per Whitelist geprüft werden.
  • „Nur diesen einen" Wert verketten. Es gibt keine sichere Ausnahme. Wenn er vom Benutzer stammt, binden Sie ihn.

Fazit

Prepared Statements teilen eine Abfrage in eine kompilierte SQL-Vorlage und die Werte, die sie ausfüllen, auf. Diese Trennung macht sie sowohl sicher (injection-geschützt) als auch schnell (einmal geparst, viele Male ausgeführt). Verwenden Sie MySQLis ?-Platzhalter oder PDOs benannte :value-Platzhalter, aber binden Sie Benutzereingaben immer, anstatt SQL von Hand zu bauen.

Lesen Sie weiter in den verwandten Kapiteln: Mit MySQL verbinden, Daten einfügen, Daten auswählen und die mysqli_prepare()-Referenz.

Übungen

Übung
Was ist das Ziel der Verwendung von MySQL Prepared Statements?
Was ist das Ziel der Verwendung von MySQL Prepared Statements?
Was this page helpful?