我在数据库中有一个唯一的列,名为 ip
BINARY(16)使用PHP函数将IP地址转换为IP地址(不排序规则)后,将其存储在此列中
$store_ip = inet_pton($ip);
Run Code Online (Sandbox Code Playgroud)
当我尝试两次插入相同的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());
}
Run Code Online (Sandbox Code Playgroud)
我插入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;
}
Run Code Online (Sandbox Code Playgroud)
Pau*_*gel 14
首先,解决方法非常简单:如果要同时存储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')]);
Run Code Online (Sandbox Code Playgroud)
并在数据库中找到以下值:
0x7F000001000000000000000000000000
Run Code Online (Sandbox Code Playgroud)
如您所见-这是一个4字节的二进制值(0x7F000001),右边用零填充,以适合16字节的固定长度列。
现在,当您尝试使用
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = ?");
$stmt->execute([inet_pton('127.0.0.1')]);
Run Code Online (Sandbox Code Playgroud)
发生以下情况:PHP将值0x7F000001作为参数发送,然后将其与存储的值进行比较0x7F000001000000000000000000000000。但是由于两个不同长度的二进制值永远不会相等,因此WHERE条件将始终返回FALSE。你可以尝试一下
SELECT 0x00 = 0x0000
Run Code Online (Sandbox Code Playgroud)
它将返回0(FALSE)。
注意:固定长度的非二进制字符串(CHAR(N))的行为不同。
我们可以使用显式强制转换作为解决方法:
$stmt = $db->prepare("SELECT * FROM ips WHERE ip = CAST(? as BINARY(16))");
$stmt->execute([inet_pton('127.0.0.1')]);
Run Code Online (Sandbox Code Playgroud)
它将找到该行。但是如果我们看看我们得到了什么
var_dump(inet_ntop($stmt->fetch(PDO::FETCH_OBJ)->ip));
Run Code Online (Sandbox Code Playgroud)
我们会看到
string(8) "7f00:1::"
Run Code Online (Sandbox Code Playgroud)
但这不是(真的)我们尝试存储的内容。现在,当我们尝试存储时7f00:1::,我们将得到一个重复的密钥错误,尽管我们尚未存储任何IPv6地址。
因此,再次:使用VARBINARY(16),您可以使代码保持不变。如果您存储许多IPv4地址,您甚至可以节省一些存储空间。
我不会回答为什么你的代码没有按预期工作,因为我不太清楚。感谢@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]);
Run Code Online (Sandbox Code Playgroud)
与这个
//query 2
$stmt = $db->prepare("SELECT * FROM `votes` WHERE ip=INET6_ATON(?)");
$stmt->execute([$_SERVER['REMOTE_ADDR']]);
Run Code Online (Sandbox Code Playgroud)
不用说,不要这样做(查询 3)-
//query 3
$stmt = $db->prepare("SELECT * FROM `votes` WHERE INET6_NTOA(ip)= ?");
$stmt->execute([$_SERVER['REMOTE_ADDR']]);
Run Code Online (Sandbox Code Playgroud)
(因为数据库会讨厌你让它对表中的每个IP记录进行转换)
从我短暂的经验来看,我发现每当我有机会让数据库而不是应用程序层(PHP)做某事时,就让数据库立即做。让 MySQL 变胖,PHP 变瘦 =) ,就像胖模型和瘦控制器所说的那样。
当您在数据库中完成大部分工作时,这将使您的数据库可以独立于 PHP 代码更好地工作(它不需要它),这使您的数据库更加可移植。
例如,如果您想将系统从使用 、 的基于云的网络系统转变为使用语言的PHP本地系统,开发人员会喜欢您减少代码编写,因为大部分工作已经编写并完成由 MySQL 提供。.net.net
另一个例子,你的应用程序成功了,你现在有更多的客户想要你的应用程序,你只需为他们提供另一台服务器并仅在其上安装MySQL,并且由于大部分工作是由数据库完成的,因此你的应用程序扩展比安装更容易一个完整的 Web 服务器,并为 PHP 进行扩展。
| 归档时间: |
|
| 查看次数: |
592 次 |
| 最近记录: |