我目前有一个包含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。
答案 0 :(得分:2)
假设您正在处理IPv4地址,每个地址只不过是32位。
有MySQL INET_NTOA
函数负责通过IP返回字符串。
所以,你可能想要使用smth:
SELECT ... FROM ... WHERE INET_NTOA(...) LIKE (...)
希望它有所帮助。
UPD:为了提高工作效率我建议您更新表格,为IP的字符串表示添加新的CHAR(16)
字段,并填充该字段的触发器ON UPDATE
值INET_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%'