标记模式 - 在单独运行查询时快速,在一个SELECT中运行时速度慢

时间:2011-12-19 15:29:55

标签: mysql performance optimization subquery tagging

我有一个奇怪的性能问题,用于创建“按标签过滤”小部件的查询,用于类似美味的书签webapp。如果运行尽可能少的单独查询,则特定的,相对复杂的查询执行的速度会快很多(1000到10000倍)。

我在以下环境中测试过它:

  • Windows XP / MySQL 5.1.37(服务器和客户端)
  • Ubuntu 11.10 / MySQL 5.1.58(服务器和客户端)

问题没有出现在小型开发数据库中。我在生产使用期间,在数据库中的记录大量增加(目前在link_tags表和11K唯一标签中大约100K行)中捕获了它。

我使用以下数据库架构:

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),因此不需要进一步说明。

从技术上讲,有问题的查询(下面)检索与给定标签集相关的标签(特别是附加到由指定标签集标记的链接的所有标签),并计算每个找到的标签和标签集的链接数。 / p>

[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

[REPLACEMENT QUERY 1]

SELECT id FROM tags t2 WHERE tag IN (?)

[Results in 0,0011 seconds]

2)查找给定标签集的所有link_id(交集!)。

[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组和组标签的所有标签。

[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错误?

原始查询的EXPLAIN结果:

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 个答案:

答案 0 :(得分:2)

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

但是,解释计划会非常有用。