2

我有这个查询:

SELECT `country`
FROM `geoip_base`
WHERE 1840344811 BETWEEN `start` AND `stop`

它严重使用索引(使用,但解析表的大部分)并且工作太慢。我尝试使用 ORDER BY 和 LIMIT,但没有帮助。

“开始 <= 1840344811 AND 1840344811 <= 停止” 工作类似。

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 行。

解释查询“... BETWEEN START AND STOP ORDER BY START LIMIT 1”:使用键stop并获得 24099 行。没有顺序和限制,mysql 不使用键并获取所有行。

4

5 回答 5

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 BYLIMIT条件,它们很重要。

此查询类似于:

SELECT  *
FROM    geoip_base
WHERE   `start` <= 1840344811
        AND `stop` >= 1840344811
ORDER BY
        `start` DESC
LIMIT 1;

使用ORDER BY / LIMIT使查询选择降序索引扫描,start该扫描将在第一个匹配时停止(即在与您输入的start最接近的范围内IP)。stop 上的附加过滤器将仅检查范围是否包含 this IP

由于您的范围不相交,因此此范围或根本没有范围将包含IP您所追求的。

于 2011-04-21T13:36:59.310 回答
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 块写入数据库中作为开始和停止列将使此功能更加可行。

在具有约 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')));

~ < 0.030 秒

资料来源:MBRTouches(g1,g2) - https://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html#function_mbrtouches

于 2017-10-20T15:20:25.103 回答
0

您的表格设计已关闭。

您正在使用十进制但不允许任何零。您立即花费 5 个字节来存储这样一个数字,简单的 INT 就足够了(4 个字节)。

之后,您创建复合主键(5 + 5 个字节),后跟 2 个唯一约束(每个 5 个字节),有效地使您的索引文件与数据文件的大小几乎相同。那样的话,无论你索引什么都是极其无效的。

使用 LIMIT 不会强制 MySQL 使用索引,至少不是您构建查询的方式。将会发生的是,MySQL 将获取满足条件的数据集,然后丢弃不符合偏移量-限制的行。

此外,使用 MySQL 的受保护关键字(例如 START 和 STOP)是一个坏主意,您永远不应该使用受保护的关键字来命名列。

有用的是您按原样创建主键并且不单独索引列。此外,将 MySQL 配置为使用更多内存将加快执行速度。

出于测试目的,我创建了一个类似于您的表,我定义了 and 的复合键startstop使用了以下查询:

SELECT `country` FROM table WHERE 1500 BETWEEN `start` AND `stop` AND start >= 1500

我的表是 InnoDB 类型的,我插入了 100k 行,查询以这种方式检查 87 行并在几毫秒内执行,我的缓冲池大小是测试机器上内存的 90%。这可能会深入了解优化您的查询/数据库实例。

于 2011-04-21T14:00:03.043 回答
0

从 GEODATA 中选择 id start_ip <=(select INET_ATON('113.0.1.63')) AND end_ip >=(select INET_ATON('113.0.1.63')) ORDER BY start_ip DESC LIMIT 1;

于 2013-09-02T12:47:31.107 回答
0

Michael JV 的上述示例将不起作用: SELECT countryFROM table WHERE 1500 BETWEEN startAND stopAND start >= 1500

BETWEEN start AND stop 与 start <= 1500 AND end >= 1500 相同

因此,您在同一子句中有 start <= 1500 AND start >= 1500 。因此,它成功的唯一方法是 start=1500,因此优化器知道使用起始索引。

于 2013-12-06T19:02:17.277 回答