我在配备 4GB 内存的 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
希望有人能帮助我加快速度。
最佳答案
我在您的查询中看到几个 WTF:
您使用了两个
LEFT OUTER JOIN
,但随后您按可能没有匹配项的c.name
列分组。所以也许你真的不需要外部连接?如果是这种情况,您应该使用内部联接,因为外部联接通常速度较慢。您正在按
c.name
分组,但这会为您的选择列表中的所有其他列提供不明确的结果。 IE。在按c.name
分组的每个分组中,这些列中可能有多个值。您很幸运您使用的是 MySQL,因为此查询只会在任何其他 RDBMS 中产生错误。这是一个性能问题,因为
<GROUP BY
可能导致您在 EXPLAIN 中看到的“using temporary; using filesort
”。这是一个臭名昭著的性能 killer ,它可能是此查询花费 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 表
。它可以通过读取 stats_id_category
表的索引来简单地获取它的计数,这应该会减少很多工作。
关于mysql - 优化 MySQL 查询,耗时将近 20 秒!,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1382549/