mysql在两列之间选择工作太慢

fjaof16o  于 2023-01-25  发布在  Mysql
关注(0)|答案(5)|浏览(132)

我有这样的疑问:

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

索引的使用很糟糕(使用,但是解析表的大部分),而且运行太慢。我试过使用ORDERBY和LIMIT,但是没有帮助。
“启动〈= 1840344811和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行。
查询“......按起始限值1在起始和停止顺序之间”的解释:使用键stop并得到24099行。没有顺序和限制,mysql不使用键并得到所有行。

huus2vyu

huus2vyu1#

如果您的表是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)

您可能会对本文感兴趣:

或者,如果您的范围不相交(从数据库的性质来看,除非它们不相交),您可以在geoip_base.start上创建一个UNIQUE索引,并使用以下查询:

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

798qvoo8

798qvoo82#

而Quassnoi的答案https://stackoverflow.com/a/5744860/1095353是完美的。MySQL函数(5.7)**MBRContains(g1,g2)在使用select时并不适合IP的全部范围。MBRContains将包含[g1,g2[不包括g2。
使用
MBRTouches(g1,g2)**可以同时匹配[g1,g2]。在数据库中写入IP块作为开始列和停止列将使此函数更可行。
在具有约6 m行的数据库表(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

7gyucuyw

7gyucuyw3#

你的table设计错了。
你使用了decimal但不允许任何零,你立即花费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类型的,我插入了100 k行,查询以这种方式检查87行,并在几毫秒内执行,我的缓冲池大小是测试机器内存的90%。

pgx2nnw8

pgx2nnw84#

从地理数据中选择标识符,其中起始IP〈=(选择初始化ATON('113.0.1.63'))且结束IP〉=(选择初始化ATON('113.0.1.63'))按起始IP排序描述限制1;

kgsdhlau

kgsdhlau5#

Michael J.V.的上述示例不起作用:从表中选择country,其中startstop之间为1500,并且开始〉= 1500
开始和停止之间与开始〈= 1500和结束〉= 1500相同
因此,在同一个子句中,start〈= 1500 AND start〉= 1500,所以,只有start=1500,优化器才知道要使用start索引,这样才能成功。

相关问题