Схема тегов - быстрая, когда запросы выполняются отдельно, медленная, когда выполняется в одном SELECT - PullRequest
2 голосов
/ 19 декабря 2011

У меня странная проблема с производительностью, связанная с запросом, используемым для создания виджета «Фильтр по тегам» для веб-приложения закладок типа 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

1 Ответ

2 голосов
/ 19 декабря 2011

WHERE IN (select values from table) крайне неэффективен в MySQL и будет запускать полное сканирование таблиц и сортировку файлов все время.Как правило, вы должны заменить их на INNER JOIN.

Я думаю, это должно помочь, но я не пытался воссоздать вашу БД и не выполнял этот запрос, так что возможны опечатки.

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
    JOIN tags t on t.id = lt.tag_id
    JOIN (SELECT
                link_id
            FROM
                link_tags lt2
            JOIN links l2 on l2.id = lt2.link_id
            JOIN tags t2 on t2.id = lt2.tag_id                
            WHERE
                AND l2.created_by = ?  <-- user to filter tags for
                AND t2.tag IN (?)  <-- tags set to filter by
            GROUP BY
                link_id
            HAVING
                COUNT(*) = ?) as eligible_links on eligible_links.link_id = lt.link_id
    GROUP BY
        tag_link_hash) tmp
GROUP BY
    tag
ORDER BY
    link_count DESC,
    tag ASC

Однако, план объяснения был бы очень полезен.

...