Wenn eine Anwendung noch mysql_query() aufruft, dann läuft sie in der Regel auf einer PHP-Version, die vor 7.0 stehengeblieben ist — denn genau dort wurde die alte mysql_*-Erweiterung ersatzlos entfernt. Das ist unbequem, aber es ist selten ein Grund zur Panik. Ein System, das seit acht oder zehn Jahren Rechnungen schreibt, Bestellungen verbucht oder Termine verwaltet, hat seine Fachlogik längst bewiesen. Das Problem sitzt fast nie in den Geschäftsregeln, sondern in der Infrastruktur drumherum — und die Datenbank-API ist einer der ersten Bausteine, die man sauber austauschen kann, ohne die eigentliche Substanz zu berühren.
Dieser Artikel zeigt konkret, wie Sie von mysql zu mysqli migrieren (oder direkt zu PDO), warum ein Big Bang die schlechteste aller Optionen ist, und worauf Sie bei Prepared Statements und Fehlerbehandlung achten müssen.
Warum die alte API überhaupt weg musste
Die mysql_*-Funktionen hatten zwei strukturelle Schwächen. Erstens boten sie keine echten Prepared Statements — Parameter mussten manuell mit mysql_real_escape_string() maskiert werden, und genau dieser manuelle Schritt wurde in der Praxis regelmäßig vergessen. Das Ergebnis waren SQL-Injection-Lücken. Zweitens war die API prozedural und global: Es gab eine implizite "letzte Verbindung", was in größeren Anwendungen zu schwer nachvollziehbaren Zuständen führte.
Der Nachfolger existiert in zwei Ausprägungen: mysqli (MySQL Improved, sowohl prozedural als auch objektorientiert nutzbar) und PDO (PHP Data Objects, ein datenbankunabhängiger Layer). Beide unterstützen echte Prepared Statements. Welchen Sie wählen, hängt vom Ziel ab — dazu gleich mehr.
mysqli oder PDO — die ehrliche Antwort
Wenn Ihr Ziel ist, mit minimalem Aufwand eine lauffähige Anwendung auf einer aktuellen PHP-Version zu bekommen, ist mysqli oft der pragmatischere Weg. Die Funktionsnamen sind vielen alten Aufrufen ähnlich, und die Migration lässt sich stellenweise fast mechanisch durchführen.
Wenn Sie ohnehin planen, die Anwendung mittelfristig weiterzuentwickeln — etwa in Richtung Symfony oder Laravel — dann führt langfristig kein Weg an PDO vorbei. PDO abstrahiert den Treiber, kennt benannte Parameter (:name statt ?) und ist die Grundlage, auf der Doctrine und Eloquent aufsetzen. Meine Empfehlung: Migrieren Sie gleich auf PDO, wenn die Anwendung eine Zukunft hat. Nur wenn es rein darum geht, ein eingefrorenes System am Leben zu halten, spricht etwas für den kürzeren mysqli-Weg.
Der Kernumbau: von manueller Maskierung zu Prepared Statements
Der eigentliche Gewinn der Migration liegt nicht darin, dass die Funktionen neue Namen haben — sondern darin, dass Sie unsicheren Code durch sicheren ersetzen. Ein typischer Altbestand sieht so aus:
$id = mysql_real_escape_string($_GET['id']);
$res = mysql_query("SELECT * FROM users WHERE id = '$id'");
Mit PDO wird daraus eine Trennung von Query und Daten, bei der die Datenbank die Werte nie als Teil des SQL interpretiert:
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $_GET['id']]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
Die Verbindung selbst konfigurieren Sie einmalig so, dass Fehler als Exceptions geworfen werden und keine stille Emulation im Hintergrund läuft:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
Das ERRMODE_EXCEPTION ist wichtig: Der alte Code hat Fehler oft über mysql_error() geprüft — oder gar nicht. Mit Exceptions fallen Probleme sofort auf, statt sich als leere Ergebnismengen zu tarnen.
Nicht alles auf einmal: der schrittweise Weg
Die verlockendste und zugleich riskanteste Idee ist, die gesamte Anwendung an einem Wochenende umzustellen. Bei tausenden verstreuten mysql_query()-Aufrufen ist das eine Einladung für subtile Fehler, die erst Wochen später auffallen. Ich gehe stattdessen so vor:
- Bestand aufnehmen. Ein
grep -rn "mysql_" .über die Codebasis zeigt das echte Ausmaß. Oft sind es weniger echte Datenbankstellen als befürchtet, weil sich vieles über wenige zentrale Hilfsfunktionen zieht. - Zentrale Datenbankschicht finden oder schaffen. Wenn die Anwendung eine eigene
db_query()-Wrapperfunktion hat, ändern Sie deren Innenleben — dann migrieren Sie einen Ort statt hundert. - Kompatibilitäts-Shim als Brücke. Für verstreute Direktaufrufe hilft eine dünne Shim-Schicht, die die alten Funktionsnamen auf mysqli oder PDO umbiegt. So läuft die Anwendung sofort wieder, und Sie bauen die Aufrufe danach in Ruhe auf echte Prepared Statements um.
- Query für Query härten. Erst wenn es läuft, ersetzen Sie die manuelle Maskierung durch parametrisierte Statements — dort, wo Nutzereingaben in Queries fließen, zuerst.
Dieser Weg hält die Anwendung in jeder Phase lauffähig und testbar. Das ist der entscheidende Unterschied zur oft vorgeschlagenen Neuentwicklung: Sie riskieren nie den kompletten Ausfall, und Sie können jederzeit anhalten, ohne ein halbfertiges System zu hinterlassen.
Die Stolpersteine, die man leicht übersieht
Ein paar Details kosten erfahrungsgemäß am meisten Zeit, wenn man sie nicht kennt. mysql_num_rows() hat kein direktes Gegenstück für Prepared Statements — bei PDO zählen Sie das Ergebnis anders, etwa über fetchAll() und count() oder eine separate COUNT(*)-Query. mysql_insert_id() wird zu $pdo->lastInsertId(). Die Zeichenkodierung stellen Sie über das DSN (charset=utf8mb4) sicher, nicht mehr über mysql_set_charset(). Und Datentypen ändern sich subtil: PDO liefert je nach Treiber und Konfiguration Zahlen mitunter als Strings zurück — Code, der auf strikte Typvergleiche setzt, kann darüber stolpern.
Ehrlicher Praxis-Tipp
Beginnen Sie nicht mit dem Code, sondern mit einem funktionierenden Test-Setup: dieselbe Anwendung auf einer neuen PHP-Version, mit einer Kopie der echten Datenbank. Erst wenn Sie reproduzierbar sehen, was bricht, migrieren Sie. Und widerstehen Sie der Versuchung, "bei der Gelegenheit" gleich die Fachlogik umzuschreiben — die läuft ja. Trennen Sie die technische Migration strikt von fachlichen Änderungen, sonst wissen Sie im Fehlerfall nicht mehr, welche der beiden Baustellen schuld ist.
Genau bei solchen Migrationen — API-Wechsel, PHP-Upgrade und Absicherung, ohne die bewährte Substanz anzutasten — unterstützt LegacyWerk, wenn intern Zeit oder Erfahrung mit Altbeständen fehlt.