Първо корекцията, която е доста проста:Ако искате да съхранявате и двата 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 адреси.