Najpierw poprawka, która jest dość prosta:jeśli chcesz przechowywać zarówno adresy IPv4, jak i IPv6, powinieneś użyć VARBINARY(16)
zamiast BINARY(16)
.
Teraz do problemu:dlaczego nie działa zgodnie z oczekiwaniami z BINARY(16)
?
Rozważmy, że mamy tabelę ips
z tylko jedną kolumną ip BINARY(16) PRIMARY KEY
.Domyślny lokalny adres IPv4 przechowujemy za pomocą
$stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);
i znajdź następującą wartość w bazie danych:
0x7F000001000000000000000000000000
Jak widzisz - jest to 4-bajtowa wartość binarna (0x7F000001
) uzupełniony zerami w prawo, aby zmieścić 16-bajtową kolumnę o stałej długości.
Kiedy teraz spróbujesz go znaleźć za pomocą
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
dzieje się tak:PHP wysyła wartość 0x7F000001
jako parametr, który jest następnie porównywany z zapisaną wartością 0x7F000001000000000000000000000000
.Ale ponieważ dwie wartości binarne o różnej długości nigdy nie są równe, warunek WHERE zawsze zwróci FALSE. Możesz to wypróbować za pomocą
SELECT 0x00 = 0x0000
co zwróci 0
(FAŁSZ).
Uwaga:zachowanie jest inne dla ciągów niebinarnych o stałej długości (CHAR(N)
).
Jako obejście możemy użyć jawnego przesyłania:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
i znajdzie rząd. Ale jeśli spojrzymy na to, co otrzymujemy
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
zobaczymy
string(8) "7f00:1::"
Ale to nie jest (naprawdę) to, co próbowaliśmy przechowywać. A kiedy teraz próbujemy przechowywać 7f00:1::
, pojawi się błąd zduplikowanego klucza , chociaż nigdy nie zapisaliśmy jeszcze żadnego adresu IPv6.
Więc jeszcze raz:użyj VARBINARY(16)
, i możesz zachować swój kod nietknięty. Zaoszczędzisz nawet trochę miejsca, jeśli przechowujesz wiele adresów IPv4.