Я начал исследовать, почему некоторые поиски в админке 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 может вернуть больше совпадений в целом.