Först korrigeringen, som är ganska enkel:Om du vill lagra både IPv4- och IPv6-adresser, bör du använda VARBINARY(16) istället för BINARY(16) .
Nu till problemet:Varför fungerar det inte som förväntat med BINARY(16) ?
Tänk att vi har en tabell ips med endast en kolumn ip BINARY(16) PRIMARY KEY .Vi lagrar den lokala IPv4-adressen som standard med
$stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);
och hitta följande värde i databasen:
0x7F000001000000000000000000000000
Som du ser - det är ett 4 byte binärt värde (0x7F000001 ) högerutfylld med nollor för att passa kolumnen med fast längd på 16 bytes.
När du nu försöker hitta den med
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
följande händer:PHP skickar värdet 0x7F000001 som parameter som sedan jämförs med det lagrade värdet 0x7F000001000000000000000000000000 .Men eftersom två binära värden av olika längd aldrig är lika, kommer WHERE-villkoret alltid att returnera FALSE. Du kan prova det med
SELECT 0x00 = 0x0000
vilket returnerar 0 (FALSKT).
Obs:Beteendet är annorlunda för icke-binära strängar med fast längd (CHAR(N) ).
Vi skulle kunna använda explicit casting som en lösning:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
och den kommer att hitta raden. Men om vi tittar på vad vi får
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
vi får se
string(8) "7f00:1::"
Men det är inte (egentligen) vad vi har försökt lagra. Och när vi nu försöker lagra 7f00:1:: , kommer vi att få ett duplicerat nyckelfel , även om vi aldrig har lagrat någon IPv6-adress ännu.
Så än en gång:Använd VARBINARY(16) , och du kan behålla din kod orörd. Du sparar till och med en del lagringsutrymme om du lagrar många IPv4-adresser.