Zuerst die Lösung, die ganz einfach ist:Wenn Sie sowohl IPv4- als auch IPv6-Adressen speichern möchten, sollten Sie VARBINARY(16)
verwenden statt BINARY(16)
.
Nun zum Problem:Warum funktioniert es mit BINARY(16)
nicht wie erwartet ?
Stellen Sie sich vor, wir haben eine Tabelle ips
mit nur einer Spalte ip BINARY(16) PRIMARY KEY
.Wir speichern die standardmäßige lokale IPv4-Adresse mit
$stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);
und finden Sie den folgenden Wert in der Datenbank:
0x7F000001000000000000000000000000
Wie Sie sehen, handelt es sich um einen 4-Byte-Binärwert (0x7F000001
) rechts mit Nullen aufgefüllt, um in die 16-Byte-Spalte mit fester Länge zu passen.
Wenn Sie jetzt versuchen, es mit
zu finden$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
Folgendes passiert:PHP sendet den Wert 0x7F000001
als Parameter, der dann mit dem gespeicherten Wert 0x7F000001000000000000000000000000
verglichen wird .Aber da zwei Binärwerte unterschiedlicher Länge niemals gleich sind, wird die WHERE-Bedingung immer FALSE zurückgeben.Sie können es mit
SELECT 0x00 = 0x0000
was 0
zurückgibt (FALSCH).
Hinweis:Das Verhalten ist anders für nicht binäre Zeichenfolgen mit fester Länge (CHAR(N)
).
Wir könnten explizites Casting als Problemumgehung verwenden:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
und es wird die Zeile finden. Aber wenn wir uns ansehen, was wir bekommen
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
wir werden sehen
string(8) "7f00:1::"
Aber das ist (wirklich) nicht das, was wir zu speichern versucht haben. Und wenn wir jetzt versuchen, 7f00:1::
zu speichern , erhalten wir einen Fehler wegen doppeltem Schlüssel ,obwohl wir noch nie eine IPv6-Adresse gespeichert haben.
Also nochmal:Verwenden Sie VARBINARY(16)
, und Sie können Ihren Code unverändert lassen. Sie sparen sogar Speicherplatz, wenn Sie viele IPv4-Adressen speichern.