我有一个奇怪的性能问题,用于创建“按标签过滤”小部件的查询,用于类似美味的书签webapp。如果运行尽可能少的单独查询,则特定的,相对复杂的查询执行的速度会快很多(1000到10000倍)。
我在以下环境中测试过它:
问题没有出现在小型开发数据库中。我在生产使用期间,在数据库中的记录大量增加(目前在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
答案 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
但是,解释计划会非常有用。