首页 > 解决方案 > 无法选择 ip=inet_pton($ip) 的位置

问题描述

我在数据库中有一个名为的唯一列ip

BINARY(16)IP 地址在使用 PHP 函数转换后存储在此列中(没有排序规则)

$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;
}

标签: phpmysqlpdo

解决方案


首先修复,这很简单:如果你想同时存储 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(FALSE)。

注意:对于固定长度的非二进制字符串 ( 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 地址,您甚至可以节省一些存储空间。


推荐阅读