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.