我在带有4GB Ram的Macbook Pro 2.53ghz上运行以下查询:
SELECT
c.id AS id,
c.name AS name,
c.parent_id AS parent_id,
s.domain AS domain_name,
s.domain_id AS domain_id,
NULL AS stats
FROM
stats s
LEFT JOIN stats_id_category sic ON s.id = sic.stats_id
LEFT JOIN categories c ON c.id = sic.category_id
GROUP BY
c.name
完成大约需要17秒。
说明:
alt text http://img7.imageshack.us/img7/1364/picture1va.png
表格:
信息:
Number of rows: 147397
Data size: 20.3MB
Index size: 1.4MB
表:
CREATE TABLE `stats` (
`id` int(11) unsigned NOT NULL auto_increment,
`time` int(11) NOT NULL,
`domain` varchar(40) NOT NULL,
`ip` varchar(20) NOT NULL,
`user_agent` varchar(255) NOT NULL,
`domain_id` int(11) NOT NULL,
`date` timestamp NOT NULL default CURRENT_TIMESTAMP,
`referrer` varchar(400) default NULL,
KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=147398 DEFAULT CHARSET=utf8
信息第二表:
Number of rows: 1285093
Data size: 11MB
Index size: 17.5MB
第二张表:
CREATE TABLE `stats_id_category` (
`stats_id` int(11) NOT NULL,
`category_id` int(11) NOT NULL,
KEY `stats_id` (`stats_id`,`category_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
信息第三表:
Number of rows: 161
Data size: 3.9KB
Index size: 8KB
第三张表:
CREATE TABLE `categories` (
`id` int(11) NOT NULL auto_increment,
`parent_id` int(11) default NULL,
`name` varchar(40) NOT NULL,
`questions_category_id` int(11) NOT NULL default '0',
`rank` int(2) NOT NULL default '0',
PRIMARY KEY (`id`),
KEY `id` (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=205 DEFAULT CHARSET=latin1
希望有人能帮我加快速度。
答案 0 :(得分:3)
我在你的查询中看到了几个WTF:
您使用了两个LEFT OUTER JOIN
,但是您可以按c.name
列进行分组,该列可能没有匹配项。那么也许你真的不需要外连接?如果是这种情况,则应使用内连接,因为外连接通常较慢。
您按c.name
分组,但这会为您的选择列表中的每个其他列提供不明确的结果。即c.name
每个分组中的这些列中可能有多个值。你很幸运,你正在使用MySQL,因为这个查询只会在任何其他RDBMS中出错。
这是性能问题,因为GROUP BY
可能会导致您在EXPLAIN中看到“using temporary; using filesort
”。这是一个臭名昭着的性能杀手,这可能是这个查询耗时17秒的最大原因。由于不清楚你为什么要使用GROUP BY
(不使用聚合函数,违反单值规则),所以你似乎需要重新考虑这一点。
您按c.name
分组,但没有UNIQUE
约束。理论上你可以有多个具有相同名称的类别,这些类别将集中在一个组中。我想知道为什么你不按c.id
分组,如果你想要每个类别一组。
SELECT NULL AS stats
:我不明白为什么你需要这个。这有点像创建一个你永远不会使用的变量。它不应该损害性能,但它只是另一个WTF让我觉得你没有想到这个问题。
您在评论中说,您正在寻找每个类别的访问者数量。但是,您的查询没有任何汇总功能,例如SUM()
或COUNT()
。您的选择列表包含s.domain
和s.domain_id
,这对每位访问者都有所不同,对吧?那么,如果每个类别只有一行,那么您期望在结果集中有什么价值呢?这也不是一个性能问题,它只是意味着您的查询结果不会告诉您任何有用的信息。
您的stats_id_category
表在其两列上有索引,但没有主键。因此,您可以轻松获得重复的行,这意味着您的访问者数量可能不准确。您需要删除该冗余索引并使用主键。我首先在该主键中订购category_id
,因此连接可以利用索引。
ALTER TABLE stats_id_category DROP KEY stats_id,
ADD PRIMARY KEY (category_id, stats_id);
现在你可以消除你的一个联接,如果你需要计算的只是访问者数量:
SELECT c.id, c.name, c.parent_id, COUNT(*) AS num_visitors
FROM categories c
INNER JOIN stats_id_category sic ON (sic.category_id = c.id)
GROUP BY c.id;
现在查询根本不需要读取stats
表,甚至不需要读取stats_id_category table
。它只需通过读取stats_id_category
表的索引即可获得计数,这应该可以消除大量工作。
答案 1 :(得分:0)
您缺少所提供信息(类别)中的第三个表格。
此外,您正在进行LEFT JOIN然后在GROUP BY中使用正确的表(可能是所有NULLS)似乎很奇怪。您最终会将所有不匹配的行分组在一起,这是您的意图吗?
最后,你能为SELECT提供一个EXPLAIN吗?
答案 2 :(得分:0)
答案 3 :(得分:0)
我同意比尔的观点。第2点非常重要。查询甚至没有逻辑意义。此外,简单的事实是没有where语句意味着你必须撤回stats表中的每一行,这似乎是140000左右。然后它必须对所有数据进行排序,以便它可以执行GROUP BY 。这是因为sort [O(n log n)]然后查找重复项[O(n)]要快于查找重复项而不排序数据集[O(n ^ 2)?? ]。