范围查询的高效数据模型

时间:2013-12-12 09:58:51

标签: mysql

我很难想到一个有效的模型来描述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的范围。

2 个答案:

答案 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_ipto_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)