16

我正在尝试以有效的方式将 IPv6 地址存储在 MySQL 5.0 中。我已经阅读了与此相关的其他问题,例如这个。该问题的作者最终选择了两个 BIGINT 字段。我的搜索还发现了另一种常用的机制:使用 DECIMAL(39,0) 来存储 IPv6 地址。我对此有两个问题。

  1. 与 2*BIGINT 等其他方法相比,使用 DECIMAL(39,0) 的优点和缺点是什么?
  2. 如何(在 PHP 中)将inet_pton()返回的二进制格式转换为MySQL 可用的十进制字符串格式,以及如何转换回来以便可以使用 inet_ntop() 进行漂亮打印?
4

3 回答 3

31

We went for a VARBINARY(16) column instead and use inet_pton() and inet_ntop() to do the conversions:

https://github.com/skion/mysql-udf-ipv6

The functions can be loaded into a running MySQL server and will give you INET6_NTOP and INET6_PTON in SQL, just as the familiar INET_NTOA and INET_ATON functions for IPv4.

Edit: There are compatible functions in MySQL now, just with different names. Only use the above if you are on pre-5.6 MySQL and are looking for a convenient future upgrade path.

于 2009-10-24T22:18:14.493 回答
19

以下是我现在用来将 IP 地址从 DECIMAL(39,0) 格式转换为 DECIMAL(39,0) 格式的函数。它们被命名为 inet_ptod 和 inet_dtop 用于“表示到十进制”和“十进制到表示”。它需要 PHP 中的 IPv6 和 bcmath 支持。

/**
 * Convert an IP address from presentation to decimal(39,0) format suitable for storage in MySQL
 *
 * @param string $ip_address An IP address in IPv4, IPv6 or decimal notation
 * @return string The IP address in decimal notation
 */
function inet_ptod($ip_address)
{
    // IPv4 address
    if (strpos($ip_address, ':') === false && strpos($ip_address, '.') !== false) {
        $ip_address = '::' . $ip_address;
    }

    // IPv6 address
    if (strpos($ip_address, ':') !== false) {
        $network = inet_pton($ip_address);
        $parts = unpack('N*', $network);

        foreach ($parts as &$part) {
            if ($part < 0) {
                $part = bcadd((string) $part, '4294967296');
            }

            if (!is_string($part)) {
                $part = (string) $part;
            }
        }

        $decimal = $parts[4];
        $decimal = bcadd($decimal, bcmul($parts[3], '4294967296'));
        $decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616'));
        $decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336'));

        return $decimal;
    }

    // Decimal address
    return $ip_address;
}

/**
 * Convert an IP address from decimal format to presentation format
 *
 * @param string $decimal An IP address in IPv4, IPv6 or decimal notation
 * @return string The IP address in presentation format
 */
function inet_dtop($decimal)
{
    // IPv4 or IPv6 format
    if (strpos($decimal, ':') !== false || strpos($decimal, '.') !== false) {
        return $decimal;
    }

    // Decimal format
    $parts = array();
    $parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0);
    $decimal = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336'));
    $parts[2] = bcdiv($decimal, '18446744073709551616', 0);
    $decimal = bcsub($decimal, bcmul($parts[2], '18446744073709551616'));
    $parts[3] = bcdiv($decimal, '4294967296', 0);
    $decimal = bcsub($decimal, bcmul($parts[3], '4294967296'));
    $parts[4] = $decimal;

    foreach ($parts as &$part) {
        if (bccomp($part, '2147483647') == 1) {
            $part = bcsub($part, '4294967296');
        }

        $part = (int) $part;
    }

    $network = pack('N4', $parts[1], $parts[2], $parts[3], $parts[4]);
    $ip_address = inet_ntop($network);

    // Turn IPv6 to IPv4 if it's IPv4
    if (preg_match('/^::\d+.\d+.\d+.\d+$/', $ip_address)) {
        return substr($ip_address, 2);
    }

    return $ip_address;
}
于 2009-08-13T10:11:44.050 回答
0

十进制(39)

优点:

  • 适用于基本算术运算符(例如 + 和 -)。
  • 适用于基本索引(精确或范围)。
  • 格式显示友好。

缺点:

  • 可以接受 IPv6 的超出范围的值。
  • 不是一种非常有效的存储机制。
  • 可能会导致混淆哪些数学运算符或函数有效,哪些无效。

二进制(16)...

优点:

  • 精确表示的最有效格式。
  • 适用于基本索引(精确和范围)。
  • 适用于 8 位倍数的前缀的前缀索引。
  • 仅存储有效的 IPv6 值(尽管不保证有效寻址)。
  • 更高版本的 MySQL 具有支持这种格式与 IPv6 表示的转换(但不支持 4in6)的函数。

缺点:

  • 对展示不友好。
  • 对用于数字的运算符或函数不友好。

二进制(39)...

这适用于完整地址(即使是 4in6 也使用 hexdec)。也可以是 ascii 而不是二进制。

优点:

  • 人类可读(如果您可以调用 IPv6)。
  • 支持基本索引(精确和范围)。
  • 支持 4 位的倍数的前缀索引。
  • 直接兼容 IPv6。无需转换。

缺点:

  • 不适用于任何数学函数或运算符。
  • 最低效的存储。
  • 可以允许无效的表示。

奇事:

  • 如果您想要不区分大小写之类的内容,则会变得复杂。
  • IPv6 有其他显示格式,尽管使用这些格式会更复杂,例如您可以拥有同一地址的两种表示形式,或者您会丢失范围查找。甚至可能最终不得不使其长度为 45 个字节或使用 varchar/varbinary。
  • 这种变化可以支持将地址保留为最初收到的地址。这可能很少需要,但当它失去很多好处时。
  • 删除具有完整格式的分隔符,并将其存储为十六进制字符串,以减少麻烦并提高效率。如果前缀索引很重要 (BINARY(128)),你可以采取很长的路要走。

BIGINT 未签名 * 2

优点:

  • 与数学运算符和函数一起使用,但需要注意的是必须在两列周围做额外的事情。
  • 高效但同样需要注意的是,它是两列会增加一些开销。
  • 适用于基本索引(精确、范围)。
  • 当前缀为 64 位时,使用前缀索引。
  • 显示友好的格式。

缺点:

  • 两列使它成为非原子的并且意味着对它的许多操作加倍。

奇事:

  • 许多现代语言和系统提供 64 位整数,但不是无符号数。签名是有问题的。负数呈现为低于正数,但它们的位序列实际上更高。出于这个原因,通常使用 4 * INT UNSIGNED。
  • 同样,人们可能会将其分解为前缀索引,并且您至少可以达到 8 位(TINYINT UNSIGNED)。有些人可能还会使用 BIT(1) 类型进行完整前缀索引,假设 MySQL 正确地在位类型上放置索引。
  • 同样,对于四列,由于计算期间的位松弛(计算中的中间值仍然可以是 64 位),具有讽刺意味的是,一些需要从另一个进行的操作更容易。

概括

人们会出于不同的原因使用不同的格式。向后兼容性可能是原因之一,这取决于对 IPv4 所做的工作。其他人则取决于地址的使用方式和围绕它的优化。您可能会看到使用了不止一种方法。

B16 是一种很好的默认方法,因为它是最高效且最轻松的。

对于 PHP 中的转换,如果您研究以下内容,您可以手动进行:

  • gmp 或 bcmath
  • PHP 的数字处理和位运算符,特别注意对 int 或 float 的限制以及依赖于它们的函数,否则这些函数可能看起来很有用
  • IPv6 格式
  • 打包/解包,bin2hex/hex2bin。

但是,我建议使用通用库来处理 IPv6 的各种显示格式。

于 2018-03-04T03:51:33.910 回答