我在数据库中有一个唯一的列,名为ip
使用PHP函数转换IP地址后,其IP地址以BINARY(16)
的形式存储在此列中(无排序规则)
$store_ip = inet_pton($ip);
当我尝试两次插入相同的IP时,它可以正常工作,并且因为它是唯一的而失败,
但是,当我尝试选择IP时它不起作用,并且总是返回FALSE(未找到)
<?php
try {
$ip = inet_pton($_SERVER['REMOTE_ADDR']);
$stmt = $db->prepare("SELECT * FROM `votes` WHERE ip=?");
$stmt->execute([$ip]);
$get = $stmt->fetch();
if( ! $get){
echo 'Not found';
}else{
echo 'Found';
}
// close connection
$get = null;
$stmt = null;
} catch (PDOException $e) {
error_log($e->getMessage());
}
我插入IP的部分:
<?php
if( ! filter_var($ip, FILTER_VALIDATE_IP)){
return FALSE;
}
$ip = inet_pton($_SERVER['REMOTE_ADDR']);
try {
$stmt = $db->prepare("INSERT INTO votes(ip, answer) VALUES(?,?)");
$stmt->execute([$ip, $answer]);
$stmt = null;
} catch (PDOException $e) {
return FALSE;
}
答案 0 :(得分:12)
首先修复,这很简单:
如果您要同时存储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地址,甚至可以节省一些存储空间。
答案 1 :(得分:4)
让我们避免这种情况,而不是为逃避BINARY
而苦苦挣扎。
INSERT INTO ips (ip) VALUES(INET6_ATON(?))
和
SELECT INET6_NTOA(ip) FROM ips WHERE ...;
那样,您只能使用人类可读的字符串。
注意:
inet_pton()
,因为现在转换是在MySQL中完成的。INET6...
函数。VARBINARY(16)
,并确保检查IPv4字符串(例如“ 1.2.3.4”)是否可以工作。答案 2 :(得分:3)
我不会回答为什么,因为我不清楚,您的代码无法按预期运行。感谢@Paul Spiegel的出色回答,他解释了原因。
在这个答案中,我只建议您使用MySQL内置函数而不是PHP。
这是我在应用程序中处理IP的方式,到目前为止,使用此模型,我没有遇到任何麻烦。
我将IP存储在varbinary(16)
列中,并使用MySQL内置函数进行转换
inet6_aton用于将IP字符串转换为二进制
inet6_ntoa用于将二进制转换为IP字符串
因此替换此代码
//query 1
$ip = inet_pton($_SERVER['REMOTE_ADDR']);
$stmt = $db->prepare("SELECT * FROM `votes` WHERE ip=?");
$stmt->execute([$ip]);
与此一起
//query 2
$stmt = $db->prepare("SELECT * FROM `votes` WHERE ip=INET6_ATON(?)");
$stmt->execute([$_SERVER['REMOTE_ADDR']]);
不用说不要这样做(查询3)-
//query 3
$stmt = $db->prepare("SELECT * FROM `votes` WHERE INET6_NTOA(ip)= ?");
$stmt->execute([$_SERVER['REMOTE_ADDR']]);
(因为数据库会讨厌您对表中的每个IP记录进行转换)
根据我的短暂经验,我发现只要有机会让数据库代替应用程序层(PHP)做某事,就让数据库立即做。使MySQL变得胖,而PHP变得瘦=),就像胖模型和瘦控制器说的那样。
当您在数据库中进行大部分工作时,这将使您的数据库可以独立于PHP代码(不需要)而更好地工作,这使数据库更具可移植性。
例如,如果您想将系统从使用PHP
的基于云的网络转变为使用.net
语言的本地系统,则.net
开发人员会因为您的代码更少而爱您,因为大多数工作已经由MySQL编写并完成。
另一个例子,您的应用程序成功了,您现在有更多的客户端想要您的应用程序,您将为他们提供另一台服务器并仅在其上安装MySQL,并且由于大多数工作是由数据库完成的,因此您的应用程序可以扩展比安装完整的Web服务器更容易,并且还可以对PHP进行扩展。