лимит 1 в объединенной таблице? - PullRequest
0 голосов
/ 06 марта 2019

1001 * столы * ip2country имеет 250 тыс. Строк (ip-диапазоны вставляются по возрастанию from_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)

1 Ответ

1 голос
/ 07 марта 2019

Вот аналогичный пример:

У меня есть таблица с 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;
...