两列之间的mysql选择工作得太慢了

时间:2011-04-21 12:48:15

标签: mysql select indexing between

我有这个问题:

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不使用密钥并获取所有行。

5 个答案:

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

此查询与此类似:

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以使用更多内存可以加快执行速度。

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

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 startstop并开始&gt; = 1500

BETWEEN开始和停止 是相同的 start&lt; = 1500 AND end&gt; = 1500

因此,你在同一条款中开始&lt; = 1500 AND start&gt; = 1500。因此,只有它成功的方法是start = 1500,因此优化器知道使用起始索引。