MySQL BETWEEN查询未使用索引

时间:2020-07-05 07:41:12

标签: mysql sql indexing mariadb

我在一个表中有geoip数据,network_start_ipnetwork_end_ipvarbinary(16)列,结果为INET6_ATON(ip_start/end)作为值。另外2列是经度和纬度。

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

不使用这些索引吗?

result of EXPLAIN

该查询大约需要3秒钟的时间,无法在生产环境中使用它。

4 个答案:

答案 0 :(得分:2)

这是行不通的,因为引用了两列-确实很难优化。假设没有重叠的IP范围,则可以将查询重构为:

var imageArray = [];
    $(document).on("click", ".showPrv", function () {
        $("#dropzone").each(function () {
            $(".dz-image-preview").each(function () {
                $(".dz-image").each(function () {
                  const src = $(this).find("img").attr("src")
                  if(imageArray.indexOf(src) < 0)
                    imageArray.push(src)
                });
            });
        });
    });

内部查询应在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'); 上的索引。 MySQL(我认为MariaDB)在升序排序方面比在降序排序方面做得更好。

答案 1 :(得分:1)

感谢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

查询响应时间现在为0.002至.004秒。很棒!

答案 2 :(得分:0)

此查询是否给您正确的结果?在搜索整数表示形式时,您的起始IP /结束IP似乎存储为二进制字符串。 我首先要确保network_start_ip和network_last_ip是IP地址的整数表示形式的无符号INT字段。假设您仅使用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_start_ip,network_last_ip)作为主键。

答案 3 :(得分:0)

这是一个棘手的问题。没有简单的解决方案。

之所以艰难,是因为它有效

   start <= 123  AND
   last  >= 123

无论可用什么索引,Optimizer都将与其中一个或另一个一起使用。使用INDEX(start, ...),它将选择start <= 123,它将扫描索引的第一部分。其他条款也是如此。其中一个扫描的索引大于索引的一半,另一个扫描的索引较少,但不足以值得使用索引。将其移至PRIMARY KEY会在某些情况下有所帮助,但几乎不值得付出任何努力。

最重要的是,不管您使用INDEX还是PRIMARY KEY的方式做什么,大多数 IP常数将导致查询超过1.5秒。 >

您的起始/最后IP范围是否重叠?如果是这样,那就增加了复杂性。特别是,重叠可能会使Gordon的LIMIT 1失效。

我的解决方案涉及要求不重叠的区域。 IP上的任何空白都需要IP的“无主”范围。这是因为只有一个start_ip。 last_ip小于表中下一项的开始。请参阅http://mysql.rjweb.org/doc.php/ipranges(其中包括用于IPv4和IPv6的代码。)

同时,经度/经度的DOUBLE太高了:http://mysql.rjweb.org/doc.php/latlng#representation_choices

相关问题