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)
这是一个类似的例子:
我有一个包含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毫秒的时间内执行。
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;