mysql выбор между двумя столбцами работает слишком медленно - PullRequest
2 голосов
/ 21 апреля 2011

У меня есть этот запрос:

SELECT `country`
FROM `geoip_base`
WHERE 1840344811 BETWEEN `start` AND `stop`

Он плохо использует индекс (использует, но анализирует большую часть таблицы) и работает слишком медленно.Я пытался использовать ORDER BY и LIMIT, но это не помогло.

"start <= 1840344811 И 1840344811 <= stop" работает аналогично. </p>

CREATE TABLE IF NOT EXISTS `geoip_base` (
  `start` decimal(10,0) NOT NULL,
  `stop` decimal(10,0) NOT NULL,
  `inetnum` char(33) collate utf8_bin NOT NULL,
  `country` char(2) collate utf8_bin NOT NULL,
  `city_id` int(11) NOT NULL,
  PRIMARY KEY  (`start`,`stop`),
  UNIQUE KEY `start` (`start`),
  UNIQUE KEY `stop` (`stop`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

Таблица содержит 57 424 строки.

Объясните для запроса "... МЕЖДУ ЗАПУСКОМ И ОСТАНОВКОЙ ЗАКАЗА ПО ПРЕДЕЛУ ПУСКА 1": используйте клавишу stop и получите 24099 строк.Без порядка и лимита mysql не использует ключи и получает все строки.

Ответы [ 5 ]

5 голосов
/ 21 апреля 2011

Если ваша таблица MyISAM, вы можете улучшить этот запрос, используя SPATIAL индексы:

ALTER TABLE
        geoip_base
ADD     ip_range LineString;

UPDATE  geoip_base
SET     ip_range =
        LineString
                (
                Point(-1, `start`),
                Point(1, `stop`)
                );

ALTER TABLE
        geoip_base
MODIFY  ip_range NOT NULL;

CREATE SPATIAL INDEX
        sx_geoip_range ON geoip_base (ip_range);

SELECT  country
FROM    geoip_base
WHERE   MBRContains(ip_range, Point(0, 1840344811)

Эта статья может вас заинтересовать:

В качестве альтернативы, если ваши диапазоны не пересекаются (и исходя из характера базы данных, за исключением их), вы можете создатьUNIQUE индексировать по geoip_base.start и использовать этот запрос:

SELECT  *
FROM    geoip_base
WHERE   1840344811 BETWEEN `start` AND `stop`
ORDER BY
        `start` DESC
LIMIT 1;

Обратите внимание на условия ORDER BY и LIMIT, они важны.

Этот запрос похож на этот:

SELECT  *
FROM    geoip_base
WHERE   `start` <= 1840344811
        AND `stop` >= 1840344811
ORDER BY
        `start` DESC
LIMIT 1;

Использование ORDER BY / LIMIT делает запрос выбора сканирования по убыванию индекса на start, который остановится при первом совпадении (т. Е. В диапазоне с start, ближайшим к введенному вами IP).Дополнительный фильтр при останове просто проверит, содержит ли диапазон этот IP.

Поскольку ваши диапазоны не пересекаются, либо этот диапазон, либо вообще никакой диапазон не будет содержать IP, который вы ищете.

1 голос
/ 20 октября 2017

Пока ответ Кассной https://stackoverflow.com/a/5744860/1095353 совершенно в порядке. Функция MySQL (5.7) MBRContains (g1, g2) не подходит для полного диапазона IP-адресов при использовании выбора. MBRContains будет содержать [g1, g2 [] не включая g2.

Использование MBRTouches (g1, g2) позволяет сопоставить оба [g1, g2]. Наличие блоков IP, записанных в базе данных в качестве столбцов start и stop, сделает эту функцию более жизнеспособной.

В таблице базы данных с ~ 6 м строками (AWS db.m4.xlarge)

SELECT *, AsWKT(`ip_range`) AS `ip_range`
FROM `geoip_base` where `start` <= 1046519788 AND `stop` >= 1046519788;

~ 2-5 секунд

SELECT *, AsWKT(`ip_range`) AS `ip_range`
FROM `geoip_base` where MBRTouches(`ip_range`, Point(0,  INET_ATON('XX.XX.XX.XX')));

~ <0,030 секунд </p>

Источник: MBRTouches (g1, g2) - https://dev.mysql.com/doc/refman/5.7/en/spatial-relation-functions-mbr.html#function_mbrtouches

0 голосов
/ 06 декабря 2013

Приведенный выше пример от Michael J.V. не сработает: ВЫБРАТЬ country ИЗ таблицы ГДЕ 1500 МЕЖДУ start И stop И НАЧАТЬ> = 1500

МЕЖДУ НАЧАТЬ И ОСТАНОВИТЬ такой же как начало <= 1500 И конец> = 1500

Таким образом, у вас есть начало <= 1500 И начало> = 1500 в том же пункте. Таким образом, единственный способ добиться успеха - это если start = 1500, и поэтому оптимизатор знает, как использовать начальный индекс.

0 голосов
/ 02 сентября 2013

SELECT id ИЗ ГЕОДАННЫХ, ГДЕ start_ip <= (выберите INET_ATON ('113.0.1.63')) И end_ip> = (выберите INET_ATON ('113.0.1.63')) ORDER BY start_ip DESC LIMIT 1;

0 голосов
/ 21 апреля 2011

Ваш дизайн стола выключен.

Вы используете десятичную дробь, но не допускаете никаких нулей. Вы немедленно тратите 5 байтов для хранения такого числа, и достаточно простого INT (4 байта).

После этого вы создаете составной первичный ключ (5 + 5 байт), за которым следуют 2 уникальных ограничения (по 5 байт каждое), что делает ваш индексный файл почти того же размера, что и файл данных. Таким образом, независимо от того, что вы указали, крайне неэффективно.

Использование LIMIT не заставляет MySQL использовать индексы, по крайней мере, не так, как вы построили свой запрос. Что произойдет, так это то, что MySQL получит набор данных, удовлетворяющий условию, а затем отбросит строки, которые не соответствуют смещению-пределу.

Кроме того, использование защищенных ключевых слов MySQL (таких как START и STOP) - плохая идея, вам следует никогда называть столбцы с использованием защищенных ключевых слов.

Что было бы полезно, так это создать первичный ключ таким, какой он есть, и не индексировать столбцы отдельно. Кроме того, настройка MySQL для использования большего объема памяти ускорит выполнение.

В целях тестирования я создал таблицу, аналогичную вашей, я определил составной ключ start и stop и использовал следующий запрос:

SELECT `country` FROM table WHERE 1500 BETWEEN `start` AND `stop` AND start >= 1500

Моя таблица имеет тип InnoDB, у меня вставлено 100 тыс. Строк, запрос проверяет 87 строк таким образом и выполняется за несколько миллисекунд, мой размер пула буферов составляет 90% памяти на моей тестовой машине. Это может дать представление об оптимизации вашего экземпляра query / db.

...