优化MySQL查询,大约需要20秒!

时间:2009-09-05 05:30:13

标签: mysql performance optimization indexing

我在带有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

希望有人能帮我加快速度。

4 个答案:

答案 0 :(得分:3)

我在你的查询中看到了几个WTF:

  1. 您使用了两个LEFT OUTER JOIN,但是您可以按c.name列进行分组,该列可能没有匹配项。那么也许你真的不需要外连接?如果是这种情况,则应使用内连接,因为外连接通常较慢。

  2. 您按c.name分组,但这会为您的选择列表中的每个其他列提供不明确的结果。即c.name每个分组中的这些列中可能有多个值。你很幸运,你正在使用MySQL,因为这个查询只会在任何其他RDBMS中出错。

    这是性能问题,因为GROUP BY可能会导致您在EXPLAIN中看到“using temporary; using filesort”。这是一个臭名昭着的性能杀手,这可能是这个查询耗时17秒的最大原因。由于不清楚你为什么要使用GROUP BY(不使用聚合函数,违反单值规则),所以你似乎需要重新考虑这一点。

  3. 您按c.name分组,但没有UNIQUE约束。理论上你可以有多个具有相同名称的类别,这些类别将集中在一个组中。我想知道为什么你不按c.id分组,如果你想要每个类别一组。

  4. SELECT NULL AS stats:我不明白为什么你需要这个。这有点像创建一个你永远不会使用的变量。它不应该损害性能,但它只是另一个WTF让我觉得你没有想到这个问题。

  5. 您在评论中说,您正在寻找每个类别的访问者数量。但是,您的查询没有任何汇总功能,例如SUM()COUNT()。您的选择列表包含s.domains.domain_id,这对每位访问者都有所不同,对吧?那么,如果每个类别只有一行,那么您期望在结果集中有什么价值呢?这也不是一个性能问题,它只是意味着您的查询结果不会告诉您任何有用的信息。

  6. 您的stats_id_category表在其两列上有索引,但没有主键。因此,您可以轻松获得重复的行,这意味着您的访问者数量可能不准确。您需要删除该冗余索引并使用主键。我首先在该主键中订购category_id,因此连接可以利用索引。

    ALTER TABLE stats_id_category DROP KEY stats_id, 
      ADD PRIMARY KEY (category_id, stats_id);
    
  7. 现在你可以消除你的一个联接,如果你需要计算的只是访问者数量:

    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)

哈利森是对的;我们需要另一张桌子。不过,我首先要将category_id的索引添加到stats_id_category。

答案 3 :(得分:0)

我同意比尔的观点。第2点非常重要。查询甚至没有逻辑意义。此外,简单的事实是没有where语句意味着你必须撤回stats表中的每一行,这似乎是140000左右。然后它必须对所有数据进行排序,以便它可以执行GROUP BY 。这是因为sort [O(n log n)]然后查找重复项[O(n)]要快于查找重复项而不排序数据集[O(n ^ 2)?? ]。