非常简单的问题但很难找到解决方案。 具有2,498,739行的地址表具有min_ip和max_ip字段的字段。这些是过滤表的核心锚点。
查询非常简单。
SELECT *
FROM address a
WHERE min_ip < value
AND max_ip > value;
因此,为min_ip和max_ip创建索引以使查询更快是合乎逻辑的。
为以下内容创建索引。
CREATE INDEX ip_range ON address (min_ip, max_ip) USING BTREE;
CREATE INDEX min_ip ON address (min_ip ASC) USING BTREE;
CREATE INDEX max_ip ON address (max_ip DESC) USING BTREE;
我确实试图创建第一个选项(min_ip和max_ip的组合),但它没有用,所以我准备了至少3个索引,为MySQL提供了更多的索引选择选项。 (请注意,此表非常静态,更多的是查找表)
+------------------------+---------------------+------+-----+---------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+---------------------+------+-----+---------------------+-----------------------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| network | varchar(20) | YES | | NULL | |
| min_ip | int(11) unsigned | NO | MUL | NULL | |
| max_ip | int(11) unsigned | NO | MUL | NULL | |
+------------------------+---------------------+------+-----+---------------------+-----------------------------+
现在,应该直接使用min_ip和max_ip作为过滤条件来查询表。
EXPLAIN
SELECT *
FROM address a
WHERE min_ip < 2410508496
AND max_ip > 2410508496;
查询执行了大约0.120到0.200秒的事情。但是,在负载测试中,查询会迅速降低性能。 MySQL服务器CPU使用率只需几个同时查询就可以达到100%的CPU使用率,并且性能会迅速降低并且不会扩展。 mysql服务器上的慢速查询已打开10秒或更长时间,最终选择查询在负载测试几秒钟后显示在日志中。 所以我用解释检查了查询,发现它确实没有使用索引。
解释计划结果
id select_type table type possible_keys key key_len ref rows Extra
------ ----------- ------ ------ ---------------------- ------ ------- ------ ------- -------------
1 SIMPLE a ALL ip_range,min_ip,max_ip (NULL) (NULL) (NULL) 2417789 Using where
有趣的是,它能够将ip_range,ip_min和ip_max确定为潜在的索引,但从不使用任何一个,如关键列所示。 我知道我可以使用FORCE INDEX并尝试使用解释计划。
EXPLAIN
SELECT *
FROM address a
FORCE INDEX (ip_range)
WHERE min_ip < 2410508496
AND max_ip > 2410508496;
使用FORCE INDEX结果解释计划
id select_type table type possible_keys key key_len ref rows Extra
------ ----------- ------ ------ ------------- -------- ------- ------ ------- -----------------------
1 SIMPLE a range ip_range ip_range 4 (NULL) 1208894 Using index condition
使用FORCE INDEX,是的,它使用ip_range索引作为键,行显示查询中不使用FORCE INDEX的子集,即2,417,789的1,208,894。 所以肯定,使用索引应该有更好的性能。 (除非我误解了解释结果)
但更有趣的是,经过几次测试后,我发现在某些情况下,即使没有FORCE INDEX,MySQL也会使用索引。我的观察是当值很小时,它会使用索引。
EXPLAIN
SELECT *
FROM address a
WHERE min_ip < 508496
AND max_ip > 508496;
解释结果
id select_type table type possible_keys key key_len ref rows Extra
------ ----------- ------ ------ ---------------------- -------- ------- ------ ------ -----------------------
1 SIMPLE a range ip_range,min_ip,max_ip ip_range 4 (NULL) 1 Using index condition
所以,只是让我感到困惑的是,基于值传递给select查询,MySQL决定何时使用索引以及何时不使用索引。 我无法想象什么是确定何时使用索引传递给查询的特定值的基础。我明白这一点 如果在WHERE条件中没有匹配的索引,则可能不会使用index但在这种情况下,ip_range索引非常清楚 是一个基于min_ip的索引,max_ip列适用于这种情况下的WHERE条件。
但我遇到的更大问题是,其他查询呢?我是否必须大规模地测试这些查询。 但即便如此,随着数据的增长,我可以依赖并期望MySQL使用索引吗? 是的,我总是可以使用FORCE INDEX来确保它使用索引。但这不是适用于所有数据库的标准SQL。 ORM框架在生成SQL时可能无法支持FORCE INDEX语法,并且它会将您的查询与索引名称紧密耦合。
不确定是否有人遇到过这个问题,但这对我来说似乎是一个非常大的问题。
答案 0 :(得分:3)
完全同意Vatev和其他人。 MySQL不仅如此。扫描表有时比先查看索引然后在磁盘上查找相应的条目要便宜。
当它确实使用索引的唯一时间是,当它是覆盖索引时,这意味着查询中的每一列(当然对于这个特定的表)都存在于索引中。意思是,如果您只需要网络列
SELECT network
FROM address a
WHERE min_ip < 2410508496
AND max_ip > 2410508496;
然后是一个覆盖索引,如
CREATE INDEX ip_range ON address (min_ip, max_ip, network) USING BTREE;
只会查看索引,因为根本不需要在磁盘上查找其他数据。并且整个索引可以保存在内存中。
答案 1 :(得分:0)
这样的范围非常难以优化。但我有a technique。它需要不重叠的范围,并且只存储start_ip,而不是end_ip(可以从&#39; next&#39;记录中有效获得)。它提供了存储的例程来隐藏凌乱的代码,涉及ORDER BY ... LIMIT 1
和其他技巧。对于大多数操作而言,它不会击中多个数据块,这与那些倾向于获取一半或全部表格的明显方法不同。
答案 2 :(得分:0)
我同意上述所有答案。但你可以尝试只制作一个复合材料 像这样的索引:
create index ip_rang on address (min_ip ASC,max_ip DESC) using BTREE;
如您所知,索引也有使用磁盘空间的缺点,因此请考虑使用的最佳索引。