Първо корекцията, която е доста проста:Ако искате да съхранявате и двата IPv4 и IPv6 адреса, трябва да използвате VARBINARY(16) вместо BINARY(16) .
Сега към проблема:Защо не работи според очакванията с BINARY(16) ?
Помислете, че имаме таблица ips само с една колона ip BINARY(16) PRIMARY KEY .Ние съхраняваме локалния IPv4 адрес по подразбиране с
$stmt = $db->prepare("INSERT INTO ips(ip) VALUES(?)");
$stmt->execute([inet_pton('127.0.0.1')]);
и намерете следната стойност в базата данни:
0x7F000001000000000000000000000000
Както виждате - това е 4 байтова двоична стойност (0x7F000001 )подпълнено отдясно с нули, за да пасне на колоната с фиксирана дължина от 16 байта.
Когато сега се опитате да го намерите с
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
се случва следното:PHP изпраща стойността 0x7F000001 като параметър, който след това се сравнява със съхранената стойност 0x7F000001000000000000000000000000 .Но тъй като две двоични стойности с различна дължина никога не са равни, условието WHERE винаги ще връща FALSE. Можете да опитате с
SELECT 0x00 = 0x0000
което ще върне 0 (НЕВЕРНО).
Забележка:Поведението е различно за недвоични низове с фиксирана дължина (CHAR(N) ).
Бихме могли да използваме изрично кастинг като заобиколно решение:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
и ще намери реда. Но ако погледнем какво получаваме
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
ще видим
string(8) "7f00:1::"
Но това не е (в действителност) това, което се опитахме да съхраняваме. И когато сега се опитваме да съхраняваме 7f00:1:: , ще получим грешка с дублиран ключ , въпреки че никога досега не сме съхранявали IPv6 адрес.
Така че още веднъж:Използвайте VARBINARY(16) , и можете да запазите кода си недокоснат. Дори ще спестите малко място за съхранение, ако съхранявате много IPv4 адреси.