MySQL показывает «возможные_ключи», но не использует его - PullRequest
0 голосов
/ 19 марта 2019

У меня есть таблица с более чем миллионом записей и около 42 столбцов. Я пытаюсь выполнить запрос SELECT для этой таблицы, который занимает минуту, чтобы выполнить. Чтобы сократить время выполнения запроса, я добавил индекс в таблицу, но этот индекс не используется.

Структура таблицы следующая. Хотя в таблице 42 столбца, я показываю только те, которые имеют отношение к моему запросу

CREATE TABLE `tas_usage` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `userid` varchar(255) DEFAULT NULL,
  `companyid` varchar(255) DEFAULT NULL,
  `SERVICE` varchar(2000) DEFAULT NULL,
  `runstatus` varchar(255) DEFAULT NULL,
  `STATUS` varchar(2000) DEFAULT NULL,
  `servertime` datetime DEFAULT NULL,
  `machineId` varchar(2000) DEFAULT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=2992891 DEFAULT CHARSET=latin1

Индекс, который я добавил, выглядит следующим образом

ALTER TABLE TAS_USAGE ADD INDEX last_quarter (SERVERTIME,COMPANYID(20),MACHINEID(20),SERVICE(50),RUNSTATUS(10));

Мой запрос SELECT

EXPLAIN SELECT DISTINCT t1.COMPANYID, t1.USERID, t1.MACHINEID FROM TAS_USAGE t1 
LEFT JOIN TAS_INVALID_COMPANY INVL ON INVL.COMPANYID = t1.COMPANYID
LEFT JOIN TAS_INVALID_MACHINE INVL_MAC_ID ON INVL_MAC_ID.MACHINEID = t1.MACHINEID
WHERE t1.SERVERTIME >= '2018-10-01 00:00:00' AND t1.SERVERTIME <= '2018-12-31 00:00:00' AND 
INVL.companyId IS NULL AND INVL_MAC_ID.machineId IS NULL AND 
t1.SERVICE NOT IN ('credentialtest%', 'webupdate%') AND  
t1.RUNSTATUS NOT IN ('Failed', 'Failed Failed', 'Failed Success', 'Success Failed', '');

ОБЪЯСНИТЬ результат выглядит следующим образом

+----+-------------+-------------+------------+--------+-----------------------+-----------------------+---------+-----------------------------+---------+----------+------------------------------------------------+
| id | select_type | table       | partitions | type   | possible_keys         | key                   | key_len | ref                         | rows    | filtered | Extra                                          |
+----+-------------+-------------+------------+--------+-----------------------+-----------------------+---------+-----------------------------+---------+----------+------------------------------------------------+
|  1 | SIMPLE      | t1          | NULL       | ALL    | last_quarter          | NULL                  | NULL    | NULL                        | 1765296 |    15.68 | Using where; Using temporary                   |
|  1 | SIMPLE      | INVL        | NULL       | ref    | invalid_company_index | invalid_company_index | 502     | servicerunprod.t1.companyid |       1 |   100.00 | Using where; Not exists; Using index; Distinct |
|  1 | SIMPLE      | INVL_MAC_ID | NULL       | eq_ref | machineId             | machineId             | 502     | servicerunprod.t1.machineId |       1 |   100.00 | Using where; Not exists; Using index; Distinct |
+----+-------------+-------------+------------+--------+-----------------------+-----------------------+---------+-----------------------------+---------+----------+------------------------------------------------+

Объяснение моего запроса

Я хочу выбрать все записи из таблицы TAS_USAGE

  1. , которые находятся между диапазоном дат (включая) 1 октября 2018 года и 31-го Декабрь 2018 г.
  2. , в которых нет соответствующих столбцов COMPANYID и MACHINEID таблицы TAS_INVALID_COMPANY и TAS_INVALID_MACHINE и
  3. , которые не содержат значений ('credentialtest%', 'webupdate%') в SERVICE столбец и значения («Failed», «Failed Failed», «Failed Успех »,« Неудачный успех »,« ») в RUNSTATUS столбце

Ответы [ 3 ]

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

Сосредоточив внимание на диапазоне дат, MySQL в основном имеет две опции:

  1. последовательно прочитать всю таблицу и выбросить записи, которые не соответствуют диапазону дат

  2. использовать индекс для идентификации записей в диапазоне дат, а затем искать каждую запись в таблице (используя первичный ключ) в отдельности («произвольный доступ»)

Последовательное чтение значительно быстрее, чем произвольный доступ, но вам нужно прочитать больше данных. Будет некоторая точка безубыточности, при которой использование индекса станет медленнее, чем простое чтение всего, и MySQL предполагает, что это так. Если это правильный выбор, во многом зависит от того, насколько правильно он угадал, сколько записей на самом деле находится в диапазоне. Если вы сделаете диапазон меньше, он должен использовать индекс в какой-то момент.

Если вы знаете, что (или хотите проверить, быстрее ли) использовать индекс, вы можете заставить MySQL использовать его с

... FROM TAS_USAGE t1 force index (last_quarter) LEFT JOIN ...

Вам следует протестировать его с разными диапазонами, и если вы генерируете свой запрос динамически, форсируйте индекс только тогда, когда вы достаточно уверены (поскольку MySQL не исправит вас, если, например, вы укажете диапазон, включающий все строки).

Существует один важный способ обойти медленный произвольный доступ к таблице, хотя, к сожалению, он не работает с вашим префиксным индексом, но я упомяну об этом в случае, если вы можете уменьшить размеры полей (или изменить их на поиски / перечисления). Вы можете включить каждый столбец, который необходим MySQL для оценки запроса, используя индекс покрытия :

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

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

На самом деле их вообще нельзя использовать вообще, особенно, чтобы не фильтровать записи перед выполнением произвольного доступа, так как для оценки вашего where -условия для RUNSTATUS или SERVICE, в любом случае требуется полное значение , Таким образом, вы можете проверить, например, RUNSTATUS очень важно - возможно, 99% ваших записей находятся в статусе «Сбой» - и в этом случае добавьте фильтр без префикса для просто (SERVERTIME, RUNSTATUS) (и MySQL может даже выбрать этот индекс самостоятельно).

1 голос
/ 23 марта 2019
   WHERE  t1.SERVERTIME >= '2018-10-01 00:00:00'
     AND  t1.SERVERTIME <= '2018-12-31 00:00:00'

странно.Это покрывает 3 месяца минус 1 день плюс 1 секунда.Предлагаем перефразировать следующим образом:

   WHERE  t1.SERVERTIME >= '2018-10-01'
     AND  t1.SERVERTIME  < '2018-10-01' + INTERVAL 3 MONTH

Существует несколько возможных причин, по которым INDEX(servertime, ...) не использовался и / или не был «полезен», даже если использовался:

  • Если большечем, возможно, 20% таблицы, в которой используется этот диапазон дат, использование индекса, вероятно, будет менее эффективным, чем простое сканирование таблицы.Использование индекса потребует отскока между BTree индекса и BTree данных.
  • Запуск индекса с помощью диапазона означает, что остальная часть индекса не будет использоваться. Префикс
  • Index ""(foo(10)) почти бесполезен.

Что вы можете сделать:

  • Нормализовать большинство этих строковых столбцов.Сколько у вас "машин"?Вероятно, далеко не 3 миллиона.Замена повторяющихся строк небольшим идентификатором (возможно, 2-байтовым SMALLINT UNSIGNED с максимальным значением 65 КБ) сэкономит много места в этой таблице.Это, в свою очередь, ускорит запрос и устранит необходимость в префиксе индекса.
  • Если нормализация нецелесообразна, поскольку в действительности существует более 3 миллионов различных значений, то посмотрите, сокращает ли VARCHAR.Если значение меньше 255. Префикс больше не нужен.
  • NOT IN не оптимизируется.Если вы можете инвертировать тест и сделать его IN(...), откроется больше возможностей, таких как INDEX(service, runstatus, servertime).Если у вас достаточно новая версия MySQL, я думаю, что оптимизатор переключится в индексе на два столбца IN и использует индекс для временного диапазона.
  • NOT IN ('credentialtest%', 'webupdate%') - Является ли % частью строки?Если вы используете % в качестве подстановочного знака, эта конструкция будет не работать.Вам потребуются два предложения LIKE.

Переформулируйте запрос следующим образом:

SELECT   t1.COMPANYID, t1.USERID, t1.MACHINEID
    FROM  TAS_USAGE t1
    WHERE  t1.SERVERTIME >= '2018-10-01'
      AND  t1.SERVERTIME  < '2018-10-01' + INTERVAL 3 MONTH
      AND  t1.SERVICE NOT IN ('credentialtest%', 'webupdate%')
      AND  t1.RUNSTATUS NOT IN ('Failed', 'Failed Failed',
                                'Failed Success', 'Success Failed', '')
      AND NOT EXISTS( SELECT 1 FROM  TAS_INVALID_COMPANY WHERE companyId = t1.COMPANYID )
      AND NOT EXISTS( SELECT 1 FROM  TAS_INVALID_MACHINE WHERE MACHINEID = t1.MACHINEID );

Если трио t1.COMPANYID, t1.USERID, t1.MACHINEID уникально, то избавьтесь от DISTINCT.

Поскольку в этом запросе используется только 6 (из 42) столбцов, построение индекса «покрытия», вероятно, поможет:

INDEX(SERVERTIME, SERVICE, RUNSTATUS, COMPANYID, USERID, MACHINEID)

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

0 голосов
/ 19 марта 2019

Предложение distinct мешает использованию индекса.Поскольку индекс не может быть использован для помощи в различении, mysql решил полностью не использовать индекс.

Если вы измените порядок полей в списке выбора, в индексе и в предложении where, mysqlможет решить использовать его:

ALTER TABLE TAS_USAGE ADD INDEX last_quarter (COMPANYID(20),MACHINEID(20), SERVERTIME, SERVICE(50),RUNSTATUS(10));


SELECT DISTINCT t1.COMPANYID, t1.MACHINEID, t1.USERID  FROM TAS_USAGE t1 
    LEFT JOIN TAS_INVALID_COMPANY INVL ON INVL.COMPANYID = t1.COMPANYID
    LEFT JOIN TAS_INVALID_MACHINE INVL_MAC_ID ON INVL_MAC_ID.MACHINEID = t1.MACHINEID
    WHERE 
    INVL.companyId IS NULL AND INVL_MAC_ID.machineId IS NULL AND 
    t1.SERVERTIME >= '2018-10-01 00:00:00' AND t1.SERVERTIME <= '2018-12-31 00:00:00' AND
    t1.SERVICE NOT IN ('credentialtest%', 'webupdate%') AND  
    t1.RUNSTATUS NOT IN ('Failed', 'Failed Failed', 'Failed Success', 'Success Failed', '');

Таким образом COMPANYID, MACHINEID поля становятся крайними левыми полями в различимом, где и индексе - хотя префикс может привести к тому, что индекс все еще будет отброшен.Возможно, вы захотите уменьшить количество полей varchar(255).

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