У меня странная проблема с производительностью, связанная с запросом, используемым для создания виджета «Фильтр по тегам» для веб-приложения закладок типа Delicious.Конкретный, относительно сложный запрос выполняется намного (от 1000 до 10000 раз) быстрее, если выполняется несколько отдельных запросов.
Я тестировал его в следующих средах:
- Windows XP /MySQL 5.1.37 (сервер и клиент)
- Ubuntu 11.10 / MySQL 5.1.58 (сервер и клиент)
Проблема не обнаружена в небольшой базе данных разработки.Я поймал это во время производственного использования, после большого увеличения записей в базе данных (в настоящее время около 100K строк в таблице link_tags и 11K уникальных тегов).
Я использую следующую схему БД:
CREATE TABLE IF NOT EXISTS `link_tags` (
`link_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL,
UNIQUE KEY `link_tag_id` (`link_id`,`tag_id`),
KEY `tag_id` (`tag_id`),
KEY `link_id` (`link_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
CREATE TABLE IF NOT EXISTS `tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag` varchar(255) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `tag` (`tag`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
Схема проста (см. также http://www.pui.ch/phred/archives/2005/04/tags-database-schemas.html),, поэтому не требует дополнительных пояснений.
Технически говоря, проблемный запрос (ниже) извлекает теги, связанные с данным набором тегов (в частности, все прикрепленные теги).на ссылки, помеченные указанным набором тегов) и подсчитывает количество ссылок для каждого найденного тега И набора тегов.
[ORIGINAL QUERY]
SELECT COUNT(*) AS link_count, tag FROM (
SELECT
t.tag AS tag,
CONCAT(lt.tag_id,':',lt.link_id) AS tag_link_hash
FROM
link_tags lt, tags t
WHERE
t.id = lt.tag_id
AND lt.link_id IN (
SELECT
link_id
FROM
link_tags lt2, links l2
WHERE
l2.id = lt2.link_id
AND l2.created_by = ? <-- user to filter tags for
AND lt2.tag_id IN (
SELECT id FROM tags t2 WHERE tag IN (?) <-- tags set to filter by
)
GROUP BY
link_id
HAVING
COUNT(*) = ?) <-- number of tags in filter
GROUP BY
tag_link_hash) tmp
GROUP BY
tag
ORDER BY
link_count DESC,
tag ASC
[Results in X minutes - up to 4 hours]
В производственной базе данных (как я уже упоминал - около 100K тегов link_tags и 11K) запрос выполняетсяот нескольких минут до нескольких часов (зависит от частоты появления указанных тегов). Странно, но все идет гладко, если я разделю его на несколько запросов:
1) Найдите id
s для заданных имен тегов.
[REPLACEMENT QUERY 1]
SELECT id FROM tags t2 WHERE tag IN (?)
[Results in 0,0011 seconds]
2) Найти все link_id
s для данного набора тегов (пересечение!).
[REPLACEMENT QUERY 2]
SELECT
link_id
FROM
link_tags lt2, links l2
WHERE
l2.id = lt2.link_id
AND l2.created_by = 1
AND lt2.tag_id IN ( ? ) <-- here goes imploded result of query 1
GROUP BY
link_id
HAVING
COUNT(*) = ? <-- number of tags
[Results in 0,0996 seconds]
3) Найти все теги для данного набора link_id
s и группы tags по количеству ссылок.
[REPLACEMENT QUERY 3]
SELECT COUNT(*) AS link_count, tag FROM (
SELECT
t.tag AS tag,
CONCAT(lt.tag_id,':',lt.link_id) AS tag_link_hash
FROM
link_tags lt, tags t
WHERE
t.id = lt.tag_id
AND lt.link_id IN ( ? ) <-- here goes imploded result of query 2
GROUP BY
tag_link_hash) tmp
GROUP BY
tag
ORDER BY
link_count DESC,
tag ASC
[Results in 0,0543 seconds]
У вас есть идеи, что происходит?EXPLAIN показывает примерно те же планы для большого запроса, что и для суммы разделенных.Разница заключается в количестве строк, обрабатываемых на каждом шаге (и это тоже странно).
Не могли бы вы помочь переписать исходный запрос, дать подсказку оптимизатору MySQL для его эффективного выполнения или указать на ошибку MySQL, которая вызываетэто поведение?
ПОЯСНИТЕ результаты для исходного запроса:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY <derived2> ALL N8LL N8LL N8LL N8LL 32 Using temporary; Using filesort
2 DERIVED lt index tag_id link_tag_id 8 N8LL 78162 Using where; Using index; Using temporary; Using filesort
2 DERIVED t eq_ref PRIMARY PRIMARY 4 lstack_prod.lt.tag_id 1
3 DEPENDENT t2 range PRIMARY,tag tag 767 N8LL 2 Using where; Using temporary; Using filesort
SUBQUERY
3 DEPENDENT lt2 ref link_tag_id, tag_id 4 lstack_prod.t2.id 7
SUBQUERY tag_id,link_id
3 DEPENDENT l2 eq_ref PRIMARY, PRIMARY 4 lstack_prod.lt2.link_id 1 Using where
SUBQUERY created_by