在连接表中限制1?

问题描述 投票:0回答:1

  • ip2country有250k行(通过升序from_ip插入ip范围)
  • sessions有50排

明显而缓慢(2.687秒):

SELECT
  s.*,
  ip.country
FROM
  sessions s
  JOIN ip2country ip ON s.ip_addr BETWEEN ip.from_ip AND ip.to_ip

虽然这本身很快(0.031秒):

SELECT
  *
FROM
  ip2country
WHERE
  from_ip >= 387703808
LIMIT 1

所以基本上问题归结为能够在连接表中使用LIMIT。可以这样做,它会是什么样子? (MySQL 5.7.24)

mysql join limit
1个回答
1
投票

这是一个类似的例子:

我有一个包含100个IP(32位整数)的表和一个包含1M IP范围的表​​。 (请参阅下面的架构和示例数据。)

以下查询与您的类似:

select *
from ips i join ip_ranges r
  on i.ip between r.ip_from and r.ip_to

返回100个具有相应范围的IP需要9.6秒。那是每个IP 100毫秒。如果我只搜索一个IP

select *
from ip_ranges r
where 555555555 between ip_from and ip_to

它需要100毫秒(如预期的那样)。请注意,对于IP = 1,我将得到“零”时间的结果,但对于IP = 999,999,999,我将等待200 ms。所以100毫秒是平均值。

添加LIMIT 1在这里没有帮助。但结合ORDER BY ip_from DESC,我得到的结果是“零时间”。

现在我可以尝试在子查询中为每个IP运行一个LIMIT 1

select i.ip
, (
    select ip_from
    from ip_ranges r
    where i.ip between r.ip_from and r.ip_to
    order by r.ip_from desc
    limit 1
) as ip_from
from ips i

但MySQL(在我的情况下是5.6)在这里做得很差,执行需要13秒。

所以我们所能做的就是获取所有IP并按IP执行一个查询。这至少会超过10秒。

另一种方法是生成一个UNION ALL查询,每个IP有一个子查询。您可以在应用程序中直接在SQL中使用动态预处理语句执行此操作:

set @subquery = '(
    select {ip} as ip, r.*
    from ip_ranges r
    where {ip} between ip_from and ip_to
    order by ip_from desc
    limit 1
)';

set session group_concat_max_len = 1000000000;

set @sql = (select group_concat(replace(@subquery, '{ip}', ip) separator 'union all') from ips);

prepare stmt from @sql;
execute stmt;

此查询在不到1毫秒的时间内执行。

Test schema and dada

create table ips(
    ip int unsigned primary key
);

insert into ips(ip)
    select floor(rand(1) * pow(10, 9))
    from seq1m s
    limit 100
;


create table ip_ranges(
    ip_from int unsigned not null,
    ip_to   int unsigned not null,
    primary key (ip_from, ip_to)
);

insert into ip_ranges
    select (s.seq - 1) * 1000 as ip_from
         , s.seq * 1000 - 1   as ip_to
    from seq1m s
    limit 1000000
;

seq1m是一个包含1M序列号的表。你可以用它来创建它

create table seq1m (seq int auto_increment primary key);
insert into seq1m (seq)
    select null
    from information_schema.COLUMNS a
       , information_schema.COLUMNS b
    limit 1000000;
© www.soinside.com 2019 - 2024. All rights reserved.