Используйте индексы для MySQL подзапросов или объединений по диапазонам - PullRequest
1 голос
/ 03 февраля 2020

У меня есть список запросов и соответствующих им IP-адресов (~ 2 миллиона строк). Я пытаюсь сделать простой JOIN для списка непересекающихся и полного списка диапазонов IP-адресов (~ 12 миллионов строк). Я проиндексировал диапазоны IP-адресов с помощью ip_from b_tree по возрастанию и ip_to b_tree по возрастанию.

Я испробовал несколько методов для управления объединением данных из этих двух таблиц, и все они пока оказались очень неэффективными. .

Я пробовал обычные JOIN, JOIN с максимальной разницей диапазона IP и использованием подзапросов. Использование EXPLAIN показало, что все они имеют possible_keys, не используя их. Я попытался использовать FORCE INDEX без удачи.

Обычный выбор отдельно показывает, что поиск IP должен занимать около 2 мс с SELECT * FROM ip_ranges WHERE INET_ATON(<some ip>) <= ip_to LIMIT 1;, а таблица запросов занимает около 16 мс на каждые 200 поисков.

Вот мой текущий запрос. Для возврата результатов требуется около 30 секунд просто потому, что индексы используются не полностью:

SELECT 
rs.fingerprint,
rs.ip,
ipr.country_code,
ipr.country_name,
ipr.region,
ipr.city,
ipr.isp_name,
ipr.domain_name,
ipr.usage_type
FROM requests AS rs
JOIN ip_ranges AS ipr ON INET_ATON(rs.ip) BETWEEN ipr.ip_from AND ipr.ip_to
LIMIT 10;

Итак, есть ли способ оптимизировать это для MySQL? Или лучше просто вызывать базу данных индивидуально для каждого запроса, используя Python? (соедините их вручную за пределами SQL).

Обновление:

Я сейчас попытался преобразовать каждый IP-адрес в соответствующий им числовой формат, хранящийся в DECIMAL(39) Колонка называется ip_numeric, как предлагается в ответах ниже. 39 также используется для поддержки адресов IPv6. База данных по-прежнему не использует индексные ключи для поиска диапазона.

Ответы [ 4 ]

1 голос
/ 03 февраля 2020

Вы можете добавить виртуальный столбец в таблицу и индексировать, что:

ALTER TABLE requests ADD ip_numeric bigint GENERATED ALWAYS AS (INET_ATON(ip)) virtual;

CREATE INDEX ip_numeric_ind ON requests (ip_numeric)

Затем используйте это в своем запросе:

SELECT 
rs.fingerprint,
rs.ip,
ipr.country_code,
ipr.country_name,
ipr.region,
ipr.city,
ipr.isp_name,
ipr.domain_name,
ipr.usage_type
FROM requests AS rs
JOIN ip_ranges AS ipr ON ip_numeric BETWEEN ipr.ip_from AND ipr.ip_to
LIMIT 10;
1 голос
/ 03 февраля 2020

Поскольку соединение не может быть оптимизировано по РЕЗУЛЬТАТУ ФУНКЦИИ (ваш INET_ATON IP-адреса), оно не будет использовать индекс.

Чтобы исправить это, я бы сделал следующее ... Примените INET_ATON () адреса перед вставкой в ​​файл запросов. Таким образом, IP-адрес уже соответствует стандартному формату файла. Сделайте то же самое для IP_Ranges (от и до), чтобы они также находились в надлежащей предварительно подтвержденной правильной согласованности формата.

Тогда объединение на ip не должно оцениваться / преобразовываться каждый раз, прежде чем "между "применяется к тесту.

ОБРАТНАЯ СВЯЗЬ

Индексы по столбцам, а не по функциям ... Никаких конкретных c документов, только из опыта. Индекс основан на значении КОЛОННЫ. Если вы присоединяетесь к результату функции, он должен запускать его на основе исходного столбца каждой записи. Таким образом, сохраняя предварительно вычисленное окончательное значение IP-адреса, вы теперь имеете этот правильно отформатированный адрес, и индекс может работать непосредственно на этом без преобразования. Аналогичным образом, при заполнении таблицы JOIN TO адресами from / to вы теперь предварительно преобразуете свои данные в окончательный формат для сравнения.

Очень похоже на индексы даты. Просто индекс по полю даты, а не месяц / год. Затем, когда вы запустите запрос и захотите что-то похожее на последний месяц, вы не сделаете месяц (someDateColumn) = 10, а год (someDateColumn) = 2019. Вы просто сделаете someDateColumn> = '2019-10-01' и someDateColumn <'2019-11-01'. Индекс по дате будет работать быстрее, чем функция сравнения. </p>

0 голосов
/ 04 февраля 2020

Если вы можете гарантировать, что пары from..to не перекрываются, есть способ значительно ускорить такие таблицы. Он включает в себя создание таблицы только с ip_from, зная, что ip_to на единицу меньше ip_from следующей строки.

Обсуждение, включая справочный код для IPv4 и IPv6: http://mysql.rjweb.org/doc.php/ipranges

Этот может быть редким случаем, когда CURSORs работает быстрее, чем попытка сделать это в одном запросе.

То есть выполнение 10 отдельных поисков с помощью описанной выше техники будет очень быстрым. Если вам нужно выполнить 2M поиска, мы должны начать все сначала. Мысль: Сортировка 2M и 12M; совпадать, как вы go. (А-ля часть «слияния» традиционных алгоритмов «сортировки-слияния».) (Нет, я не продумывал детали.)

0 голосов
/ 03 февраля 2020

Получить индексы для использования диапазонов может быть довольно сложно. Я рекомендую следующий подход:

  • Найти первый диапазон, где диапазон заканчивается на или после данного ip.
  • Присоединиться к таблице диапазонов, чтобы получить начальную точку
  • сравнить!

As SQL:

select r.*, ir.*
from (select r.*,
             (select ir.ip_to
              from ip_ranges ir
              where ir.ip_to >= inet_aton(r.ip)
              order by ir.ip_to
              limit 1
             ) as range_to
      from requests r
     ) r join
     ip_ranges ir
     on ir.ip_to = r.range_to
where r.ip >= ir.ip_to;

Для этого нужен индекс на ip_ranges(ip_to), как для коррелированного подзапроса, так и для конечного join.

...