Почему производительность MySQL (InnoDB) сильно варьируется? - PullRequest
1 голос
/ 13 марта 2012

Я начал исследовать, почему некоторые поиски в админке Django были действительно медленными (см. здесь ). Пройдя дальше, я обнаружил, что производительность MySQL (5.1, таблицы InnoDB) сильно варьируется от одного запроса к другому схожим. Например:

Этот запрос (ищущий 'c', 'd' и 'e' в 4 полях, связанных с 2), сгенерированный Django, занимает 89 мс и возвращает 3093 строки:

SELECT DISTINCT `donnees_artiste`.`id`
    FROM `donnees_artiste`
LEFT OUTER JOIN `donnees_artiste_evenements`
    ON (`donnees_artiste`.`id` = `donnees_artiste_evenements`.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement`
    ON (`donnees_artiste_evenements`.`evenement_id` = `donnees_evenement`.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T4
    ON (`donnees_artiste`.`id` = T4.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T5
    ON (T4.`evenement_id` = T5.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T6
    ON (`donnees_artiste`.`id` = T6.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T7
    ON (T6.`evenement_id` = T7.`id`)

WHERE (
    (`donnees_artiste`.`nom` LIKE '%c%'
  OR `donnees_artiste`.`prenom` LIKE '%c%'
  OR `donnees_evenement`.`cote` LIKE '%c%'
  OR `donnees_evenement`.`titre` LIKE '%c%' )
AND (`donnees_artiste`.`nom` LIKE '%d%'
  OR `donnees_artiste`.`prenom` LIKE '%d%'
  OR T5.`cote` LIKE '%d%'
  OR T5.`titre` LIKE '%d%' )
AND (`donnees_artiste`.`nom` LIKE '%e%'
  OR `donnees_artiste`.`prenom` LIKE '%e%'
  OR T7.`cote` LIKE '%e%'
  OR T7.`titre` LIKE '%e%' )
);

Если я заменю 'e' на 'k', так что это в основном тот же запрос, это займет 8720 мс (увеличение в 100 раз) и вернет 931 строку.

SELECT DISTINCT `donnees_artiste`.`id`
    FROM `donnees_artiste`
LEFT OUTER JOIN `donnees_artiste_evenements`
    ON (`donnees_artiste`.`id` = `donnees_artiste_evenements`.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement`
    ON (`donnees_artiste_evenements`.`evenement_id` = `donnees_evenement`.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T4
    ON (`donnees_artiste`.`id` = T4.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T5
    ON (T4.`evenement_id` = T5.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T6
    ON (`donnees_artiste`.`id` = T6.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T7
    ON (T6.`evenement_id` = T7.`id`)

WHERE (
    (`donnees_artiste`.`nom` LIKE '%c%'
  OR `donnees_artiste`.`prenom` LIKE '%c%'
  OR `donnees_evenement`.`cote` LIKE '%c%'
  OR `donnees_evenement`.`titre` LIKE '%c%' )
AND (`donnees_artiste`.`nom` LIKE '%d%'
  OR `donnees_artiste`.`prenom` LIKE '%d%'
  OR T5.`cote` LIKE '%d%'
  OR T5.`titre` LIKE '%d%' )
AND (`donnees_artiste`.`nom` LIKE '%k%'
  OR `donnees_artiste`.`prenom` LIKE '%k%'
  OR T7.`cote` LIKE '%k%'
  OR T7.`titre` LIKE '%k%' )
);

Оба эти запроса дают один и тот же EXPLAIN, так что никакой подсказки нет.

ID  SELECT_TYPE     TABLE   TYPE    POSSIBLE_KEYS   KEY     KEY_LEN     REF     ROWS    EXTRA
1   SIMPLE  donnees_artiste     ALL     None    None    None    None    4368    Using temporary; Using filesort
1   SIMPLE  donnees_artiste_evenements  ref     artiste_id,donnees_artiste_evenements_eb99df11  artiste_id  4   mmac.donnees_artiste.id     1   Using index; Distinct
1   SIMPLE  donnees_evenement   eq_ref  PRIMARY,donnees_evenements_id_index     PRIMARY     4   mmac.donnees_artiste_evenements.evenement_id    1   Using where; Distinct
1   SIMPLE  T4  ref     artiste_id,donnees_artiste_evenements_eb99df11  artiste_id  4   mmac.donnees_artiste.id     1   Using index; Distinct
1   SIMPLE  T5  eq_ref  PRIMARY,donnees_evenements_id_index     PRIMARY     4   mmac.T4.evenement_id    1   Using where; Distinct
1   SIMPLE  T6  ref     artiste_id,donnees_artiste_evenements_eb99df11  artiste_id  4   mmac.donnees_artiste.id     1   Using index; Distinct
1   SIMPLE  T7  eq_ref  PRIMARY,donnees_evenements_id_index     PRIMARY     4   mmac.T6.evenement_id    1   Using where; Distinct

Также, если я сделаю COUNT для первого запроса, это займет 11200 мсек.

SELECT COUNT(DISTINCT `donnees_artiste`.`id`)
    FROM `donnees_artiste`
LEFT OUTER JOIN `donnees_artiste_evenements`
    ON (`donnees_artiste`.`id` = `donnees_artiste_evenements`.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement`
    ON (`donnees_artiste_evenements`.`evenement_id` = `donnees_evenement`.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T4
    ON (`donnees_artiste`.`id` = T4.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T5
    ON (T4.`evenement_id` = T5.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T6
    ON (`donnees_artiste`.`id` = T6.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T7
    ON (T6.`evenement_id` = T7.`id`)

WHERE (
    (`donnees_artiste`.`nom` LIKE '%c%'
  OR `donnees_artiste`.`prenom` LIKE '%c%'
  OR `donnees_evenement`.`cote` LIKE '%c%'
  OR `donnees_evenement`.`titre` LIKE '%c%' )
AND (`donnees_artiste`.`nom` LIKE '%d%'
  OR `donnees_artiste`.`prenom` LIKE '%d%'
  OR T5.`cote` LIKE '%d%'
  OR T5.`titre` LIKE '%d%' )
AND (`donnees_artiste`.`nom` LIKE '%e%'
  OR `donnees_artiste`.`prenom` LIKE '%e%'
  OR T7.`cote` LIKE '%e%'
  OR T7.`titre` LIKE '%e%' )
);

Мой innodb_buffer_pool_size установлен высоко. У меня есть индексы для всех соответствующих полей и первичных ключей, и я уже оптимизировал свои таблицы.

Итак, почему первый запрос такой быстрый, а два других - медленный? Эти 3 запроса являются лишь примерами. Много раз я просто менял или удалял один символ из запроса, и это сильно повлияло на время запроса. Но я не вижу никакой картины.

UPDATE

Проблема с производительностью определенно связана с тем, как Django генерирует эти запросы. Все эти избыточные LEFT OUTER JOIN, объединенные в цепочку, убивают производительность. В настоящий момент мне не совсем ясно, является ли это ошибкой в ​​генераторе SQL Django, ошибкой в ​​том, как построен запрос для поля поиска, или все это работает так, как ожидают разработчики Django. Я все еще расследую, но, по крайней мере, в поведении Джанго есть одна странная вещь ...

Если я выполню этот запрос (который не обязательно эквивалентен второму, но не далеко), результаты будут получены довольно быстро (161 мс, кеша нет):

SELECT DISTINCT `donnees_artiste`.`id`
    FROM `donnees_artiste`
LEFT OUTER JOIN `donnees_artiste_evenements`
    ON (`donnees_artiste`.`id` = `donnees_artiste_evenements`.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement`
    ON (`donnees_artiste_evenements`.`evenement_id` = `donnees_evenement`.`id`)

WHERE (
    (`donnees_artiste`.`nom` LIKE '%c%'
  OR `donnees_artiste`.`prenom` LIKE '%c%'
  OR `donnees_evenement`.`cote` LIKE '%c%'
  OR `donnees_evenement`.`titre` LIKE '%c%' )
AND (`donnees_artiste`.`nom` LIKE '%d%'
  OR `donnees_artiste`.`prenom` LIKE '%d%'
  OR `donnees_evenement`.`cote` LIKE '%d%'
  OR `donnees_evenement`.`titre` LIKE '%d%' )
AND (`donnees_artiste`.`nom` LIKE '%k%'
  OR `donnees_artiste`.`prenom` LIKE '%k%'
  OR `donnees_evenement`.`cote` LIKE '%k%'
  OR `donnees_evenement`.`titre` LIKE '%k%' )
);

ВТОРОЕ ОБНОВЛЕНИЕ

Наконец, это не ошибка в Django, я уверен, что это желаемое поведение. Идея заключается в том, что при поиске по нескольким терминам поиск следующего термина выполняется по подмножеству, возвращенному предыдущим термином, поэтому для связанных полей все термины не обязательно должны находиться в одной строке, чтобы иметь матч. Для этого БД должны создать временную таблицу с каждым подмножеством и отсканировать ее. Это объясняет, почему может быть много вариаций, потому что, если первый термин соответствует только нескольким строкам, временная таблица будет маленькой, и поиск последующего термина будет быстрым (потому что они будут выполняться в маленькой таблице). Разница между этими двумя запросами невелика, но запрос Django может вернуть больше совпадений в целом.

Ответы [ 3 ]

2 голосов
/ 13 марта 2012

Я думаю, ответ заключается в том, что e в большинстве случаев находится в начале отсканированных строк и в первой искомой строке, позволяя замкнуть в цепочку условия ИЛИ, в то время как совпадения для k происходят в последнем условия и где-то в конце строки. И поскольку k содержит значительно меньше строк, больше строк должно быть отсканировано полностью без совпадений.

2 голосов
/ 13 марта 2012

Если вы используете шаблон LIKE с лидирующим символом подстановки, ваш запрос не будет работать с индексом. Использование LIKE таким способом может быть крайне неэффективным, и время его выполнения может сильно различаться. ПОЧЕМУ?

  1. Алгоритм оператора LIKE прекращает поиск строки в случае совпадения.
  2. В этом сценарии (без индекса) MySQL применяет некоторые другие алгоритмы повышения, которые могут или не могут быть применимы в некоторых случаях.

Почему использование COUNT в третьем запросе так сильно его замедляет?

Я вижу, вы используете innoDB.

innoDB не считывает количество строк из сохраненного / кэшированного значения, как это делает MyISAM (если столбец NOT NULL), потому что innoDB более оптимизирован для «записи», чем «чтения» (в отличие от MyISAM). Используя COUNT для таблицы innoDB, выполняйте либо полное сканирование таблицы, либо полное сканирование индекса каждый раз, когда вы это делаете.

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

Я подумал, что вас могло заинтересовать: Индексы MySQL

1 голос
/ 13 марта 2012

Условия типа:

WHERE column LIKE '%c%'

не может использовать индекс для column. Таким образом, эти столбцы должны быть полностью отсканированы.

У вас есть несколько таких условий, вы используете OR между ними (что гарантирует, что все эти таблицы будут отсканированы). И наконец, вы (скорее Django) добавляете DISTINCT, что, вероятно, требует окончательной сортировки файлов перед возвратом результатов.

Я не могу найти объяснение огромной разницы в производительности (100x). Возможно, первый запрос был кэширован. Можете ли вы попробовать добавить ORDER NY NULL в конце запросов и рассчитать их время?

Сгенерированный запрос также не очень хорошо разработан, потому что он, вероятно, закончится мини-декартовым соединением. Вы объединяете базовую таблицу с несколькими таблицами, которые связаны с базовой таблицей 1-ко-многим. Это является причиной низкой производительности, и план запроса поможет прояснить это.

...