我有这个问题:
SELECT `country`
FROM `geoip_base`
WHERE 1840344811 BETWEEN `start` AND `stop`
使用索引很糟糕(使用,但解析表的大部分)并且工作太慢。 我尝试过使用ORDER BY和LIMIT,但它没有帮助。
“start< = 1840344811 AND 1840344811< = stop”的工作方式类似。
CREATE TABLE IF NOT EXISTS `geoip_base` (
`start` decimal(10,0) NOT NULL,
`stop` decimal(10,0) NOT NULL,
`inetnum` char(33) collate utf8_bin NOT NULL,
`country` char(2) collate utf8_bin NOT NULL,
`city_id` int(11) NOT NULL,
PRIMARY KEY (`start`,`stop`),
UNIQUE KEY `start` (`start`),
UNIQUE KEY `stop` (`stop`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
表有57,424行。
解释查询“......按开始限制1开始和停止订单”:
使用密钥stop
并获得24099行。
没有顺序和限制,mysql不使用密钥并获取所有行。
答案 0 :(得分:5)
如果您的表格为MyISAM
,则可以使用SPATIAL
索引改进此查询:
ALTER TABLE
geoip_base
ADD ip_range LineString;
UPDATE geoip_base
SET ip_range =
LineString
(
Point(-1, `start`),
Point(1, `stop`)
);
ALTER TABLE
geoip_base
MODIFY ip_range NOT NULL;
CREATE SPATIAL INDEX
sx_geoip_range ON geoip_base (ip_range);
SELECT country
FROM geoip_base
WHERE MBRContains(ip_range, Point(0, 1840344811)
您可能会对此文章感兴趣:
或者,如果您的范围不相交(并且与数据库的性质不相交,除非它们不相交),您可以在UNIQUE
上创建geoip_base.start
索引并使用此查询:
SELECT *
FROM geoip_base
WHERE 1840344811 BETWEEN `start` AND `stop`
ORDER BY
`start` DESC
LIMIT 1;
请注意ORDER BY
和LIMIT
条件,它们很重要。
此查询与此类似:
SELECT *
FROM geoip_base
WHERE `start` <= 1840344811
AND `stop` >= 1840344811
ORDER BY
`start` DESC
LIMIT 1;
使用ORDER BY / LIMIT
会使查询选择start
上的降序索引扫描,该扫描将在第一个匹配时停止(即在start
最接近IP
的范围内你输入)。停止时的其他过滤器只会检查范围是否包含此IP
。
由于您的范围不相交,因此此范围或任何范围都不会包含您所追求的IP
。
答案 1 :(得分:1)
Quassnoi的答案https://stackoverflow.com/a/5744860/1095353完全没问题。使用select时,MySQL函数(5.7) MBRContains(g1,g2)不适合IP的全部范围。 MBRContains将包含 [g1,g2 [ 不包括g2。
使用 MBRTouches(g1,g2)允许[g1,g2]匹配。将IP块写入数据库内作为start,stop列将使此功能更加可行。
在行数约为6m的数据库表(AWS db.m4.xlarge)
上SELECT *, AsWKT(`ip_range`) AS `ip_range`
FROM `geoip_base` where `start` <= 1046519788 AND `stop` >= 1046519788;
~2-5秒
SELECT *, AsWKT(`ip_range`) AS `ip_range`
FROM `geoip_base` where MBRTouches(`ip_range`, Point(0, INET_ATON('XX.XX.XX.XX')));
〜&lt; 0.030秒
来源:MBRTouches(g1,g2) - https://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html#function_mbrtouches
答案 2 :(得分:0)
你的桌子设计已关闭。
您正在使用小数但不允许任何零。您立即花费5个字节来存储这样的数字,简单的INT就足够了(4个字节)。
之后,您创建复合主键(5 + 5个字节),然后是2个唯一约束(每个5个字节),从而有效地使您的索引文件与数据文件的大小几乎相同。 这样,无论你索引什么都是极其无效的。
使用LIMIT不会强制MySQL使用索引,至少不是你构造查询的方式。会发生什么是MySQL将获得满足条件的数据集,然后丢弃不符合offset-limit的行。
此外,使用MySQL的受保护关键字(例如START和STOP)是一个坏主意,您应该从不使用受保护的关键字命名列。
有用的是您按原样创建主键,而不是单独索引列。 此外,配置MySQL以使用更多内存可以加快执行速度。
出于测试目的,我创建了一个与您类似的表,我定义了start
和stop
的复合键,并使用了以下查询:
SELECT `country` FROM table WHERE 1500 BETWEEN `start` AND `stop` AND start >= 1500
我的表是InnoDB类型,我插入了100k行,查询以这种方式检查87行并在几毫秒内执行,我的缓冲池大小是我测试机器内存的90%。这可能会让您深入了解优化查询/数据库实例。
答案 3 :(得分:0)
SELECT id FROM GEODATA WHERE start_ip&lt; =(选择INET_ATON('113.0.1.63'))AND end_ip&gt; =(选择INET_ATON('113.0.1.63'))ORDER BY start_ip DESC LIMIT 1;
答案 4 :(得分:0)
Michael J.V.的上述例子不起作用:
SELECT country
FROM FROM WHERE 1500 BETWEEN start
和stop
并开始&gt; = 1500
BETWEEN开始和停止 是相同的 start&lt; = 1500 AND end&gt; = 1500
因此,你在同一条款中开始&lt; = 1500 AND start&gt; = 1500。因此,只有它成功的方法是start = 1500,因此优化器知道使用起始索引。