MySQL запрос с использованием GROUP BY очень медленный - PullRequest
0 голосов
/ 02 сентября 2018

У меня есть база данных, использующая следующую схему:

CREATE TABLE IF NOT EXISTS `sessions` (
  `starttime` datetime NOT NULL,
  `ip` varchar(15) NOT NULL default '',
  `country_name` varchar(45) default '',
  `country_iso_code` varchar(2) default '',
  `org` varchar(128) default '',
  KEY (`ip`),
  KEY (`starttime`),
  KEY (`country_name`)
);

(Фактическая таблица содержит больше столбцов; я включил только столбцы, по которым запрашиваю.) Движок - InnoDB.

Как видите, есть 3 индекса - на ip, starttime и country_name.

Таблица очень большая - она ​​содержит около 1,5 миллионов строк. Я выполняю различные запросы по нему, пытаясь извлечь информацию за месяц (за август 2018 года, в приведенных ниже примерах).

Запрос, подобный этому

SELECT
  UNIX_TIMESTAMP(starttime) as time_sec,
  country_iso_code AS metric,
  COUNT(country_iso_code) AS value
FROM
  sessions
WHERE
  starttime >= FROM_UNIXTIME(1533070800) AND
  starttime <= FROM_UNIXTIME(1535749199)
GROUP BY metric;

довольно медленный, но терпимый (десятки секунд), несмотря на то, что на country_iso_code нет индекса.

(Игнорировать первое, что есть в SELECT; я знаю, что это, кажется, не имеет смысла, но это требуется в инструменте, который использует результат запроса. Точно так же игнорируйте использование FROM_UNIXTIME() вместо строки даты, эта часть запроса генерируется автоматически, и я не могу ее контролировать.)

Однако такой запрос

SELECT
  country_name AS Country,
  COUNT(country_name) AS Attacks
FROM
  sessions
WHERE
  starttime >= FROM_UNIXTIME(1533070800) AND
  starttime <= FROM_UNIXTIME(1535749199)
GROUP BY Country;

невыносимо медленно - я дал ему поработать около получаса и сдался без каких-либо результатов.

Результаты EXPLAIN:

+----+-------------+----------+------------+-------+------------------------------------+--------------+---------+------+----------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys                      | key          | key_len | ref  | rows     | filtered | Extra       |
+----+-------------+----------+------------+-------+------------------------------------+--------------+---------+------+----------+----------+-------------+
|  1 | SIMPLE      | sessions | NULL       | index | starttime,starttime_2,country_name | country_name | 138     | NULL | 14771687 |    35.81 | Using where |
+----+-------------+----------+------------+-------+------------------------------------+--------------+---------+------+----------+----------+-------------+

В чем именно проблема? Должен ли я индексировать что-то еще? Возможно составной индекс на (starttime, country_name)? Я прочитал это руководство , но, может быть, я его неправильно понял?

Вот некоторые другие запросы, которые так же медленны и, вероятно, страдают от той же проблемы:

Запрос № 2:

SELECT
  ip AS IP,
  COUNT(ip) AS Attacks
FROM
  sessions
WHERE
  starttime >= FROM_UNIXTIME(1533070800) AND
  starttime <= FROM_UNIXTIME(1535749199)
GROUP BY ip;

Результаты EXPLAIN:

+----+-------------+----------+------------+-------+--------------------------+------+---------+------+----------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys            | key  | key_len | ref  | rows     | filtered | Extra       |
+----+-------------+----------+------------+-------+--------------------------+------+---------+------+----------+----------+-------------+
|  1 | SIMPLE      | sessions | NULL       | index | starttime,ip,starttime_2 | ip   | 47      | NULL | 14771780 |    35.81 | Using where |
+----+-------------+----------+------------+-------+--------------------------+------+---------+------+----------+----------+-------------+

Запрос № 3:

SELECT
  org AS Organization,
  COUNT(org) AS Attacks
FROM
  sessions
WHERE
  starttime >= FROM_UNIXTIME(1533070800) AND
  starttime <= FROM_UNIXTIME(1535749199)
GROUP BY Organization;

Результаты EXPLAIN:

+----+-------------+----------+------------+-------+---------------------------+------+---------+------+----------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys             | key  | key_len | ref  | rows     | filtered | Extra       |
+----+-------------+----------+------------+-------+---------------------------+------+---------+------+----------+----------+-------------+
|  1 | SIMPLE      | sessions | NULL       | index | starttime,starttime_2,org | org  | 387     | NULL | 14771800 |    35.81 | Using where |
+----+-------------+----------+------------+-------+---------------------------+------+---------+------+----------+----------+-------------+

Запрос № 4:

SELECT
  ip AS IP,
  country_name AS Country,
  city_name AS City,
  org AS Organization,
  COUNT(ip) AS Attacks
FROM
  sessions
WHERE
  starttime >= FROM_UNIXTIME(1533070800) AND
  starttime <= FROM_UNIXTIME(1535749199)
GROUP BY ip;

Результаты EXPLAIN:

+----+-------------+----------+------------+-------+--------------------------+------+---------+------+----------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys            | key  | key_len | ref  | rows     | filtered | Extra       |
+----+-------------+----------+------------+-------+--------------------------+------+---------+------+----------+----------+-------------+
|  1 | SIMPLE      | sessions | NULL       | index | starttime,ip,starttime_2 | ip   | 47      | NULL | 14771914 |    35.81 | Using where |
+----+-------------+----------+------------+-------+--------------------------+------+---------+------+----------+----------+-------------+

Ответы [ 2 ]

0 голосов
/ 13 сентября 2018

Еще лучше ...

Обратите внимание, что у вас нет PRIMARY KEY; это непослушно Наличие PK само по себе не улучшит производительность, но наличие PK начинается с starttime. Давайте сделаем это:

CREATE TABLE IF NOT EXISTS `sessions` (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT,   -- note
  `starttime` datetime NOT NULL,
  `ip` varchar(39) NOT NULL CHARACTER SET ascii default '',  -- note
  `country_name` varchar(45) default '',
  `country_iso_code` char(2) CHARACTER SET ascii  default '',  -- note
  `org` varchar(128) default '',
  PRIMARY KEY(starttime, id)  -- in this order
  INDEX(id)                   -- to keep AUTO_INCREMENT happy
  -- The rest are unnecessary for the queries in question:
  KEY (`ip`),
  KEY (`starttime`),
  KEY (`country_name`)
) ENGINE=InnoDB;        -- just in case you are accidentally getting MyISAM

Почему? Это позволит воспользоваться «кластеризацией» ПК с данными. Таким образом, будет сканироваться только часть таблицы, которая находится во временном диапазоне. И между индексом и данными не будет подпрыгивания. И вам не понадобится много индексов для эффективного выполнения всех случаев.

IPv6 требует до 39 байтов. Обратите внимание, что VARCHAR не позволит вам выполнить какие-либо тесты диапазона (CDR). Я могу обсудить это дальше, как вам нравится.

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

В общем, запросы вида

  SELECT column, COUNT(column)
    FROM tbl
   WHERE datestamp >= a AND datestamp <= b
   GROUP BY column

работает лучше всего, когда таблица имеет составной индекс на (datestamp, column). Зачем? Они могут быть удовлетворены с помощью сканирования индекса , вместо того, чтобы считывать все строки таблицы.

Другими словами, первая соответствующая строка для запроса может быть найдена путем случайного доступа к индексу (к первому значению метки даты). Затем MySQL может последовательно прочитать индекс и посчитать различные значения в column, пока он не достигнет последней соответствующей строки. Там нет необходимости читать фактическую таблицу; запрос выполняется только из индекса. Это делает это быстрее.

UPDATE TABLE tbl ADD INDEX date_col (datestamp, column);

создает индекс для вас.

Остерегайтесь двух вещей. Один: индексы из одного столбца не обязательно помогают агрегировать производительность запросов.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...