mysql

y4ekin9u  于 2021-07-24  发布在  Java
关注(0)|答案(4)|浏览(222)

我在一个表中有geoip数据, network_start_ip 以及 network_end_ipvarbinary(16) 结果为的列 INET6_ATON(ip_start/end) 作为价值观。另外两列是纬度和经度。

CREATE TABLE `ipblocks` (
 `network_start_ip` varbinary(16) NOT NULL,
 `network_last_ip` varbinary(16) NOT NULL,
 `latitude` double NOT NULL,
 `longitude` double NOT NULL,
 KEY `network_start_ip` (`network_start_ip`),
 KEY `network_last_ip` (`network_last_ip`),
 KEY `idx_range` (`network_start_ip`,`network_last_ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

如您所见,我为测试创建了3个索引。为什么我的(相当简单的)查询

SELECT 
    latitude, longitude
FROM
    ipblocks b
WHERE
    INET6_ATON('82.207.219.33') BETWEEN b.network_start_ip AND b.network_last_ip

不使用这些索引吗?

查询大约需要3秒钟,这对于在生产中使用它来说太长了。

uubf1zoe

uubf1zoe1#

它不起作用,因为有两个列被引用——这确实很难优化。假设没有重叠的ip范围,可以将查询重组为:

SELECT b.*
FROM (SELECT b.*
      FROM ipblocks b
      WHERE b.network_start_ip <= INET6_ATON('82.207.219.33')
      ORDER BY b.network_start_ip DESC
      LIMIT 1
     ) b
WHERE INET6_ATON('82.207.219.33') <= network_last_ip;

内部查询应在上使用索引 ipblocks(network_start_ip) . 外部查询只比较一行,因此不需要任何索引。
或作为:

SELECT b.*
FROM (SELECT b.*
      FROM ipblocks b
      WHERE b.network_last_ip >= INET6_ATON('82.207.219.33')
      ORDER BY b.network_end_ip ASC
      LIMIT 1
     ) b
WHERE network_last_ip <= INET6_ATON('82.207.219.33');

这将在上使用索引 (network_last_ip) . mysql(我认为mariadb)使用升序排序比降序排序做得更好。

bnl4lu3b

bnl4lu3b2#

多亏了gordon linoff,我找到了问题的最佳答案。

SELECT b.* FROM 
  (SELECT b.* FROM ipblocks b WHERE b.network_start_ip <= INET6_ATON('82.207.219.33') 
                              ORDER BY b.network_start_ip DESC LIMIT 1 ) 
b WHERE INET6_ATON('82.207.219.33') <= network_last_ip

现在我们选择小于 INET6_ATON(82.207.219.33) 在内部查询中,但我们将它们按降序排列,这使我们能够使用 LIMIT 1 再一次。
查询响应时间现在是.002到.004秒。太好了!

ftf50wuq

ftf50wuq3#

这个查询给你的结果正确吗?在搜索整数表示时,开始/结束IP似乎存储为二进制字符串。我首先要确保network\u start\u ip和network\u last\u ip是无符号int字段,用整数表示ip地址。这是假设您仅使用ipv4:

CREATE TABLE ipblocks_int AS
SELECT
    INET_ATON(network_start_ip) as network_start_ip,
    INET_ATON(network_last_ip) as network_last_ip,
    latitude,
    longitude
FROM ipblocks

然后使用(network\u start\u ip,network\u last\u ip)作为主键。

s8vozzvw

s8vozzvw4#

这是个棘手的问题。没有简单的解决办法。
它之所以艰难,是因为它是有效的

start <= 123  AND
   last  >= 123

不管有什么索引可用,优化器都将使用其中一个索引。与 INDEX(start, ...) ,它会选择 start <= 123 它将扫描索引的第一部分。另一条也一样。其中一个扫描了超过一半的索引,另一个扫描的更少——但还不足以让索引值得使用。把它移到 PRIMARY KEY 在某些情况下会有所帮助,但这几乎不值得努力。
底线,不管你做了什么 INDEX 或者 PRIMARY KEY ,大多数ip常量将导致查询超过1.5秒。
开始/结束ip范围重叠吗?如果是这样,那就增加了复杂性。特别是,重叠可能会使戈登的理论失效 LIMIT 1 .
我的解决方案需要不重叠的区域。IP中的任何缺口都需要“无主”IP范围。这是因为只有一个起始ip;最后一个\u ip小于表中下一项的开头。看到了吗http://mysql.rjweb.org/doc.php/ipranges (它包括ipv4和ipv6的代码。)
与此同时, DOUBLE 对于lat/lng是过度杀戮:http://mysql.rjweb.org/doc.php/latlng#representation_choices

相关问题