我很难想到一个有效的模型来描述IPv4地址数据。我希望能够在MySQL中的数据集上执行'whois'类型查找。目前我有这个:
CREATE TABLE inetnum (
`from_ip` int(11) unsigned NOT NULL,
`to_ip` int(11) unsigned NOT NULL,
`netname` varchar(40) default NULL,
`ip_txt` varchar(60) default NULL,
`descr` varchar(60) default NULL,
`country` varchar(2) default NULL,
`recurse_limit` int(11) NOT NULL default '0',
`unexpected` int(11) NOT NULL default '0',
`rir` enum('APNIC','AFRINIC','ARIN','RIPE','LACNIC') NOT NULL default 'RIPE',
PRIMARY KEY (`from_ip`,`to_ip`)
) ENGINE=MyISAM DEFAULT CHARSET=ascii;
我想做这样的询问:
SELECT *
FROM inetnum
WHERE INET_ATON('192.168.0.1') BETWEEN from_ip AND to_ip;
但由于地址范围的上限和下限保存在不同的字段中,因此会导致全表扫描:
mysql> EXPLAIN SELECT * FROM `inetnum` WHERE INET_ATON('192.168.0.1') BETWEEN from_ip AND to_ip;
+----+-------------+---------+------+---------------+------+---------+------+---------+------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+------+---------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | inetnum | ALL | NULL | NULL | NULL | NULL | 3800440 | Using where |
+----+-------------+---------+------+---------------+------+---------+------+---------+-------------+
1 row in set (0.00 sec)
(并且我确信有人会尝试指出 - 不是因为INET_ATON函数 - 使用文字整数没有区别,也没有使用< = to_ip AND> = from_ip)。
目前正在MySQL 5.0.67上运行。我只有有限的更改/升级DBMS的范围。
答案 0 :(得分:2)
实际上,您的主键在此类范围查询方面没什么意义。它仅指示<from_ip, to_ip>
元组的唯一对 - 因此,MySQL将无法使用该索引进行此类范围比较。
除非您运行的某些查询涉及主键的两个部分,否则它将无效(实际上,MySQL也将使用它 - 当选择条件使用left-subset of compound index时,但这不是您的情况)。例如,这将使用主键:
-- @x and @y are derived from somewhere else
SELECT * FROM inetnum WHERE from_ip=@x && to_ip=@y
在您的情况下,复合键可能是主键,是的,但它的唯一好处是 - 提供唯一性。因此,您可以保持原样,或创建代理id
主键(用UNIQUE
约束替换当前主键)。
改善情况的可能解决方案之一可能是 - 为from_ip
和to_ip
创建单列密钥。因为它们是整数,所以很有可能获得高基数,结果索引会有。但是,MySQL只能使用一个索引,因此,您将失去范围有效比较的“一半”。
另外你应该记住,如果大于(或小于)的比较会影响太多的行,MySQL也不会使用索引(因为很明显,因为有太多的行要选择)。
并且 - 是的,避免在WHERE
子句中使用函数。我不是说在这种情况下MySQL总是会松散索引使用(但很可能,在大多数情况下它会松散) - 但考虑一下会导致函数调用的开销。即使它很少 - 您也可以通过传递由您的应用程序形成的正确值来摆脱它。
答案 1 :(得分:2)
我找到了一个解决方案(使用空间数据类型)here on Stack overflow - 但请注意解决方案不是接受的答案 - 这是来自Quassnoi的解决方案
请投票以关闭我的问题作为副本。
但是对于在家里尝试这个的人来说,还有一个额外的复杂因素,因为我已经有了一个数据表 - 所以我使用了一个略有不同的食谱:
mysql> alter table inetnum add column netrange linestring;
Query OK, 3800440 rows affected (22.41 sec)
Records: 3800440 Duplicates: 0 Warnings: 0
mysql> create spatial index rangelookup on inetnum(netrange);
ERROR 1252 (42000): All parts of a SPATIAL index must be NOT NULL
mysql> UPDATE inetnum
-> SET netrange=GeomFromText(CONCAT('LINESTRING(', from_ip, ' -1, ', to_ip, ' 1)'))
-> ;
Query OK, 3800440 rows affected (57.42 sec)
Rows matched: 3800440 Changed: 3800440 Warnings: 0
mysql> create spatial index rangelookup on inetnum(netrange);
ERROR 1252 (42000): All parts of a SPATIAL index must be NOT NULL
mysql> alter table inetnum modify netrange linestring not null;
Query OK, 3800440 rows affected (35.84 sec)
Records: 3800440 Duplicates: 0 Warnings: 0
mysql> create spatial index rangelookup on inetnum(netrange);
Query OK, 3800440 rows affected (1 min 19.69 sec)
Records: 3800440 Duplicates: 0 Warnings: 0
mysql> SELECT COUNT(*)
-> FROM inetnum
-> WHERE INET_ATON('88.104.22.241') BETWEEN from_ip AND to_ip;
+----------+
| COUNT(*) |
+----------+
| 3 |
+----------+
1 row in set (1.19 sec)
mysql> SELECT COUNT(*)
-> FROM inetnum
-> WHERE MBRCONTAINS(netrange, GEOMFROMTEXT(CONCAT('POINT(', INET_ATON('88.104.22.241'), ' 0)')));
+----------+
| COUNT(*) |
+----------+
| 10 |
+----------+
1 row in set (0.06 sec)