【发布时间】:2015-06-09 05:27:10
【问题描述】:
我已经设法整理出一个符合我需要的查询,尽管它比我希望的要复杂。但是,对于表的大小,查询比它应该的要慢(0.17s)。原因,基于下面提供的EXPLAIN,是因为在innodb 引擎的WHERE 子句中有COUNT 对meta_relationships 表进行表扫描。
查询:
SELECT
posts.post_id,posts.post_name,
GROUP_CONCAT(IF(meta_data.type = 'category', meta.meta_name,null)) AS category,
GROUP_CONCAT(IF(meta_data.type = 'tag', meta.meta_name,null)) AS tag
FROM posts
RIGHT JOIN meta_relationships ON (posts.post_id = meta_relationships.object_id)
LEFT JOIN meta_data ON meta_relationships.meta_data_id = meta_data.meta_data_id
LEFT JOIN meta ON meta_data.meta_id = meta.meta_id
WHERE meta.meta_name = computers AND meta_relationships.object_id
NOT IN (SELECT meta_relationships.object_id FROM meta_relationships
GROUP BY meta_relationships.object_id HAVING count(*) > 1)
GROUP BY meta_relationships.object_id
此特定查询选择仅具有 computers 类别的帖子。 count > 1 的目的是排除包含computers/hardware、computers/software 等的帖子。选择的类别越多,计数越高。
理想情况下,我想让它像这样运行:
WHERE meta.meta_name IN ('computers') AND meta_relationships.meta_order IN (0)
或
WHERE meta.meta_name IN ('computers','software')
AND meta_relationships.meta_order IN (0,1)
等等。
但不幸的是这不起作用,因为它没有考虑到可能存在meta_relationships.meta_order = 2。
我试过了……
WHERE meta.meta_name IN ('computers')
GROUP BY meta_relationships.meta_order
HAVING meta_relationships.meta_order IN (0) AND meta_relationships.meta_order NOT IN (1)
但它没有返回正确的行数。
解释:
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY meta ref PRIMARY,idx_meta_name idx_meta_name 602 const 1 Using where; Using index; Using temporary; Using filesort
1 PRIMARY meta_data ref PRIMARY,idx_meta_id idx_meta_id 8 database.meta.meta_id 1
1 PRIMARY meta_relationships ref idx_meta_data_id idx_meta_data_id 8 database.meta_data.meta_data_id 11 Using where
1 PRIMARY posts eq_ref PRIMARY PRIMARY 4 database.meta_relationships.object_id 1
2 MATERIALIZED meta_relationships index NULL idx_object_id 4 NULL 14679 Using index
表/索引:
元
此表包含类别和标签名称。
索引:
主键 (meta_id)、键 idx_meta_name (meta_name)
元数据
此表包含有关类别和标签的附加数据,例如类型(类别或标签)、描述、父级、计数。
索引:
主键 (meta_data_id)、键 idx_meta_id (meta_id)
meta_relationships
这是一个连接/查找表。它包含posts_id 的外键、meta_data_id 的外键,还包含类别的顺序。
索引:
主键(relationship_id),键idx_object_id(object_id),键idx_meta_data_id(meta_data_id)
- 计数允许我只选择具有正确类别级别的帖子。例如,计算机类别的帖子只有计算机类别,但也有计算机/硬件的帖子。计数过滤掉包含这些额外类别的帖子。我希望这是有道理的。
- 我相信优化查询的关键是完全摆脱
COUNT。 -
COUNT的替代方法可能是使用meta_relationships.meta_order或meta_data.parent。 -
meta_relationships表将快速增长,并且以当前大小(约 15K 行)我希望在 100 秒而不是 10 秒内实现执行时间。 - 由于每个类别/标签的
WHERE子句中需要有多个条件,因此首选针对动态查询优化的任何答案。 - 我用sample data 创建了一个IDE。
如何优化此查询?
编辑:
我一直无法找到解决这个问题的最佳方案。这实际上是 smcjones 建议改进索引的组合,我建议为此执行 EXPLAIN 并查看 EXPLAIN Output Format 然后将索引更改为能够为您提供最佳性能的任何内容。
此外,hpf 建议在总计数中添加另一列也有很大帮助。最后,在更改索引后,我最终使用了这个查询。
SELECT posts.post_id,posts.post_name,
GROUP_CONCAT(IF(meta_data.type = 'category', meta.meta_name,null)) AS category,
GROUP_CONCAT(IF(meta_data.type = 'tag', meta.meta_name,null)) AS tag
FROM posts
JOIN meta_relationships ON meta_relationships.object_id = posts.post_id
JOIN meta_data ON meta_relationships.meta_data_id = meta_data.meta_data_id
JOIN meta ON meta_data.meta_id = meta.meta_id
WHERE posts.meta_count = 2
GROUP BY posts.post_id
HAVING category = 'category,subcategory'
除去COUNT 后,最大的性能杀手是GROUP BY 和ORDER BY,但索引是你最好的朋友。我了解到,在做GROUP BY 时,WHERE 子句非常重要,越具体越好。
【问题讨论】:
-
能否为每个表提供
SHOW CREATE TABLE tablename,尤其是meta_relation,以便我们查看索引是由什么组成的。 -
解释(英文)
NOT IN的目的;这就是表扫描的位置。 (你很幸运——在旧版本中,它的运行速度会非常慢。) -
@RickJames - 其目的是消除任何具有多个类别或标签的
object_id。 -
对我来说有点像
meta。您要选择最多有一个标签的帖子吗? -
@LeGEC - 在示例查询中是的,但我需要能够选择任意数量的类别。
标签: mysql join count query-optimization