php搜索存储在mysql中的部分IP地址为unsigned int

时间:2014-10-22 12:58:19

标签: php mysql datatables-1.10

我目前有一个包含IP地址的mysql数据库。在搜索表单上,客户端想要搜索部分IP地址并弹出(可能)许多结果。我目前将mysql中的IP地址存储为unsigned int。我使用的是PHP 5.2,因此无法访问php 5.7及其INET6_NTOA函数。

当前的数据库有超过50,000条记录并且还在继续增长,因此我不想将所有的IP转换为点分表示法,然后进行匹配 - 这看起来有点笨拙。 / p>

我有更好的方法来搜索部分IP地址吗?客户坚持这一点,所以别无选择。

PS。我在datatables-1.10中显示数据但不应该与我搜索的方式有任何关系 - 只是fyi。

4 个答案:

答案 0 :(得分:2)

假设您正在处理IPv4地址,每个地址只不过是32位。

有MySQL INET_NTOA函数负责通过IP返回字符串。

所以,你可能想要使用smth:

SELECT ... FROM ... WHERE INET_NTOA(...) LIKE (...)

希望它有所帮助。

UPD:为了提高工作效率我建议您更新表格,为IP的字符串表示添加新的CHAR(16)字段,并填充该字段的触发器ON UPDATEINET_NTOA(...)。选择这个字段就像魅力一样。

答案 1 :(得分:2)

实际上,无符号整数列已经是搜索部分ip地址匹配的最有效方法!请不要浪费你的精力和CPU时间转换回点分表示法或在某种字符串列上进行LIKE搜索。

存在几种写下部分IP地址的方法,但最后,它们都归结为带有网络掩码的基本IP。另外,假设部分地表示所有具有公共前缀的IP,那么这也等同于指定IP范围。

无论哪种方式,部分IP地址规范最终被描述为两个32位无符号整数,以与数据库列相同的格式编码。要么你有一个启动ip和结束ip,要么你有一个基本的IP和一个掩码。这些整数可以直接在SQL查询中使用,以有效地获得匹配。更好的是,如果你使用ip范围方法,那么引擎将能够利用你的ip列上的有序索引。你不能指望更好。

那么如何建立IP范围?我们这取决于你的部分地址是如何在第一个地方指定的,但假设你知道网络掩码,那么起始地址等于(基本IP和网络掩码),结束地址是((基数) ip& net mask)|(~netmask)),其中&,|和〜分别表示按位 - 和,按位 - 或按位 - 不。

<强>更新

以下是应用我所描述的策略的示例代码。

现在,自从我上次编写PHP代码以来已经很长时间了,以下内容从未执行过,所以请原谅我可能引入的任何错误。我还故意选择“扩展”每个符号场景,以使它们更容易理解,而不是将它们全部压缩到一个非常复杂的正则表达式中。

if (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [/] (\d{1,2}) $/x', $input, $r)) {
    // Four-dotted IP with number of significant bits: 123.45.67.89/24

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = intval($r[3]);
    $d = intval($r[4]);
    $mask = intval($r[5]);

} elseif (preg_match(' /^ (\d{1,3}) (?: [.] [*0] [.] [*0] [.] [*0] )? $/x', $input, $r)) {
    // Four-dotted IP with three-last numbers missing, or equals to 0 or '*':
    // 123.45, 123.45.0.0, 123.45.*.*  (assume netmask of 8 bits)

    $a = intval($r[1]);
    $b = 0;
    $c = 0;
    $d = 0;
    $mask = 8;

} elseif (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) (?: [.] [*0] [.] [*0] )? $/x', $input, $r)) {
    // Four-dotted IP with two-last numbers missing, or equals to 0 or '*':
    // 123.45, 123.45.0.0, 123.45.*.*  (assume netmask of 16 bits)

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = 0;
    $d = 0;
    $mask = 16;

} elseif (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) (?: [.] [*0] )? $/x', $input, $r)) {
    // Four-dotted IP with last number missing, or equals to 0 or *:
    // 123.45.67, 123.45.67.0, 123.45.67.*  (assume netmask of 24 bits)

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = intval($r[3]);
    $d = 0;
    $mask = 24;

} elseif (preg_match(' /^ (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) [.] (\d{1,3}) $/x', $input, $r)) {
    // Four-dotted IP: 123.45.67.89 (assume netmask of 32 bits)

    $a = intval($r[1]);
    $b = intval($r[2]);
    $c = intval($r[3]);
    $d = intval($r[4]);
    $mask = 32;

} else {
    throw new Exception('...');
}

if ($a < 0 || $a > 255) {  throw new Exception('...') };
if ($b < 0 || $b > 255) {  throw new Exception('...') };
if ($c < 0 || $c > 255) {  throw new Exception('...') };
if ($d < 0 || $d > 255) {  throw new Exception('...') };
if ($mask < 1 || $mask > 32) {  throw new Exception('...') };

$baseip = ($a << 24) + ($b << 16) + ($c << 8) + ($d);
$netmask = (1 << (32 - $mask)) - 1;

$startip = $baseip & netmask;
$endip = ($baseip & netmask) | (~netmask);

// ...

doSql( "SELECT ... FROM ... WHERE ipaddress >= ? && ipaddress <= ?", $startip, $endip);

// or

doSql( "SELECT ... FROM ... WHERE ((ipaddress & ?) = ?)", $netmask, $startip);

答案 2 :(得分:0)

就是这样。

$ip = '127.5.3';

if (preg_match('/^([0-9]*)?\.?([0-9]*)?\.?([0-9]*)?\.?([0-9]*)$/',$ip, $m)) {
  $from = (int)$m[1]*256*256*256 +(int)$m[2]*256*256 + (int)$m[3]*256 + (int)$m[4];
  // or $from = ip2long($m[1].'.'.$m[2].'.'.$m[3].'.'.$m[4]);
  $to = ($m[1]>0?$m[1]:255)*256*256*256 + ($m[2]>0?$m[2]:255)*256*256 + ($m[3]>0?$m[3]:255)*256+($m[4]>0?$m[4]:255);
  // select * from sometable where ip between $from and $to
} else
  echo "Incorrect IP";

答案 3 :(得分:-2)

由于你想要部分搜索并返回一个匹配ips的列表,我建议使用LIKE,然后在结尾使用%

SELECT ip FROM ip_table
WHERE ip LIKE '$ip%'