无法选择ip = inet_pton($ ip)的位置

时间:2019-03-25 22:21:58

标签: php mysql pdo

我在数据库中有一个唯一的列,名为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;
}

3 个答案:

答案 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 ...;

那样,您只能使用人类可读的字符串。

注意:

  • 跳过PHP中的inet_pton(),因为现在转换是在MySQL中完成的。
  • 旧版本的MySQL中不存在INET6...函数。
  • 是的,请务必使用VARBINARY(16),并确保检查IPv4字符串(例如“ 1.2.3.4”)是否可以工作。

答案 2 :(得分:3)

我不会回答为什么,因为我不清楚,您的代码无法按预期运行。感谢@Paul Spiegel的出色回答,他解释了原因。

在这个答案中,我只建议您使用MySQL内置函数而不是PHP。

这是我在应用程序中处理IP的方式,到目前为止,使用此模型,我没有遇到任何麻烦。

我将IP存储在varbinary(16)列中,并使用MySQL内置函数进行转换

  1. inet6_aton用于将IP字符串转换为二进制

  2. 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进行扩展。