Вот аналогичный пример:
У меня есть таблица с 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 секунды.Это 100 мс на IP.Если я просто ищу один IP
select *
from ip_ranges r
where 555555555 between ip_from and ip_to
Это займет около 100 мс (как и ожидалось).Обратите внимание, что я получу результат в «нулевое» время для IP = 1, но подожду 200 мс для IP = 999 999 999.Поэтому 100 мс - это среднее значение.
Добавление LIMIT 1
здесь не поможет.Но в сочетании с ORDER BY ip_from DESC
я получаю результаты в «нулевом времени».
Теперь я могу попытаться запустить один LIMIT 1
на IP в подзапросе:
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 мс.
Тестовая схема и 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;