есть ли способ оптимизации следующего запроса:
EXPLAIN EXTENDED SELECT keyword_id, ck.keyword, COUNT( article_id ) AS cnt
FROM career_article_keyword
LEFT JOIN career_keywords ck
USING ( keyword_id )
WHERE keyword_id
IN (
SELECT keyword_id
FROM career_article_keyword
LEFT JOIN career_keywords ck
USING ( keyword_id )
WHERE article_id
IN (
SELECT article_id
FROM career_article_keyword
WHERE keyword_id =9
)
AND keyword_id <>9
)
GROUP BY keyword_id
ORDER BY cnt DESC
Основная задача здесь, если у меня есть определенное keyword_id (CURRENT_KID), мне нужно найти все ключевые слова, которые когда-либо принадлежали какой-либо статье вместе с CURRENT_KID, и результат сортировки основан на количестве использования этих ключевых слов
таблицы, определенные как:
mysql> show create table career_article_keyword;
+------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| career_article_keyword | CREATE TABLE `career_article_keyword` (
`article_id` int(11) unsigned NOT NULL,
`keyword_id` int(11) NOT NULL,
UNIQUE KEY `article_id` (`article_id`,`keyword_id`),
CONSTRAINT `career_article_keyword_ibfk_1` FOREIGN KEY (`article_id`) REFERENCES `career` (`menu_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> show create table career_keywords;
+-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| career_keywords | CREATE TABLE `career_keywords` (
`keyword_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`keyword` varchar(250) NOT NULL,
PRIMARY KEY (`keyword_id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 |
+-----------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
вывод "объяснения" пугает меня
на больших данных этот запрос может убить все:) Могу ли я сделать это быстрее?
спасибо.
Глядя на ваш вывод EXPLAIN
, я был обеспокоен тем, что ваше использование подзапросов привело к неоптимальному использованию индексов. Я чувствовал (без каких-либо оправданий - и по этой причине я вполне могу ошибаться), что переписывание с использованием JOIN
может привести к более оптимизированному запросу.
Чтобы сделать это, нам нужно понять, для чего предназначен ваш запрос. Это помогло бы, если бы ваш вопрос сформулировал это, но после небольшого поцарапания головы я решил, что ваш запрос пытается получить список всех других ключевых слов, которые появляются в любой статье, содержащей определенное ключевое слово, вместе со счетом всех статей в котором отображаются эти ключевые слова.
Теперь перетащите запрос поэтапно:
Извлеките "любую статью, содержащую определенное заданное ключевое слово" (не беспокоясь о дубликатах):
SELECT ca2.article_id
FROM
career_article_keyword AS ca2
WHERE
ca2.keyword_id = 9;
Извлеките "все другие ключевые слова, которые появляются в [выше]"
SELECT ca1.keyword_id
FROM
career_article_keyword AS ca1
JOIN career_article_keyword AS ca2 ON (ca2.article_id = ca1.article_id)
WHERE
ca1.keyword_id <> 9
AND ca2.keyword_id = 9
GROUP BY ca1.keyword_id;
Извлеките "[выше] вместе со счетом всех статей, в которых отображаются эти ключевые слова:
SELECT ca1.keyword_id, COUNT(DISTINCT ca0.article_id) AS cnt
FROM
career_article_keyword AS ca0
JOIN career_article_keyword AS ca1 USING (keyword_id)
JOIN career_article_keyword AS ca2 ON (ca2.article_id = ca1.article_id)
WHERE
ca1.keyword_id <> 9
AND ca2.keyword_id = 9
GROUP BY ca1.keyword_id
ORDER BY cnt DESC;
Наконец, мы хотим добавить к выходу соответствующее ключевое слово непосредственно из таблицы career_keyword
:
SELECT ck.keyword_id, ck.keyword, COUNT(DISTINCT ca0.article_id) AS cnt
FROM
career_keywords AS ck
JOIN career_article_keyword AS ca0 USING (keyword_id)
JOIN career_article_keyword AS ca1 USING (keyword_id)
JOIN career_article_keyword AS ca2 ON (ca2.article_id = ca1.article_id)
WHERE
ca1.keyword_id <> 9
AND ca2.keyword_id = 9
GROUP BY ck.keyword_id -- equal to ca1.keyword_id due to join conditions
ORDER BY cnt DESC;
Сразу становится ясно, что ваш исходный запрос ссылался на career_keywords
дважды, тогда как этот перезаписанный запрос ссылается на эту таблицу только один раз; это само по себе может объяснить разницу в производительности - попробуйте удалить вторую ссылку на нее (т.е. где она появится в вашем первом подзапросе), поскольку она полностью избыточна там.
Оглядываясь назад на этот запрос, мы видим, что соединения выполняются в следующих столбцах:
career_keywords.keyword_id
в ck JOIN ca0
В этой таблице указан PRIMARY KEY (`keyword_id`)
, поэтому есть хороший индекс, который можно использовать для этого объединения.
career_article_keyword.article_id
в ca1 JOIN ca2
В этой таблице указано UNIQUE KEY `article_id` (`article_id`,`keyword_id`)
, и поскольку article_id
является самым левым столбцом в этом индексе, есть хороший индекс, который может быть использован для этого объединения.
career_article_keyword.keyword_id
в ck JOIN ca0
и ca0 JOIN ca1
Нет индекса, который может быть использован для этого объединения: единственный индекс, определенный в этой таблице, имеет другой столбец, article_id
слева от keyword_id
- поэтому MySQL не может найти записи keyword_id
в индексе без первого зная article_id
. Я предлагаю вам создать новый индекс, который имеет keyword_id
в качестве его самого левого столбца.
(Необходимость этого индекса могла быть одинаково проверена непосредственно из вашего исходного запроса, где ваши два внешних запроса выполняют объединения в этом столбце.)
career_article_keyword
присоединяете таблицуcareer_article_keyword
к себе (найдите статьи, содержащие ключевое слово, найдите все другие ключевые слова в этих статьях, посчитайте все статьи, содержащие эти ключевые слова), поэтому, безусловно, важно, если эта таблица станет большой; Вы пробовали сравнить свой запрос с моим пересмотренным (после создания нового индекса, который я предлагаю)?