2009-08-12 161 views
2

我找不出为什么我的查询变慢。它归结为四个表格:团队,玩家,设备和元数据。玩家和装备的记录有一个FK队,使球队成为球员和装备的父母。并且这三个表中的每行都有一个元数据记录,用于存储创建日期,创建者用户ID等内容。如何优化包含两个左连接的MySQL查询?

我想一次检索所有玩家和设备记录,属于特定团队,按创建日期排列。我从元数据表开始,通过metadata_id FK离开播放器和设备表,但是当我尝试过滤SELECT以仅检索某个团队的记录时,查询会在有很多行时减慢大的时间。

下面是该查询:

SELECT metadata.creation_date, player.id, equipment.id 
FROM 
    metadata 
    JOIN datatype  ON datatype.id   = metadata.datatype_id 
    LEFT JOIN player ON player.metadata_id = metadata.id 
    LEFT JOIN equipment ON equipment.metadata_id = metadata.id 
WHERE 
    datatype.name IN ('player', 'equipment') 
    AND (player.team_id = 1 OR equipment.team_id = 1) 
ORDER BY metadata.creation_date; 

你需要补充大量的行真正看到慢下来,大约10,000为每个表。我不明白的是,为什么如果我只在一张桌子的where子句中进行筛选,它真的很快,例如:“... AND player.team_id = 1”但是当我添加另一个以使其成为“.. AND(player.team_id = 1 OR equipment.team_id = 1)“需要很长的时间。

以下是表和数据类型。请注意,有一件事情似乎有很大帮助,但不是那么多,是metadata_id和team_id的播放器和设备上的组合键。

CREATE TABLE `metadata` (
    `id` INT(4) unsigned NOT NULL auto_increment, 
    `creation_date` DATETIME NOT NULL, 
    `datatype_id` INT(4) unsigned NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB; 
CREATE TABLE `datatype` (
    `id` INT(4) unsigned NOT NULL auto_increment, 
    `name` VARCHAR(255) NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB; 
CREATE TABLE `team` (
    `id` INT(4) unsigned NOT NULL auto_increment, 
    `metadata_id` INT(4) unsigned NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB; 
CREATE TABLE `player` (
    `id` INT(4) unsigned NOT NULL auto_increment, 
    `metadata_id` INT(4) unsigned NOT NULL, 
    `team_id` INT(4) unsigned NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB; 
CREATE TABLE `equipment` (
    `id` INT(4) unsigned NOT NULL auto_increment, 
    `metadata_id` INT(4) unsigned NOT NULL, 
    `team_id` INT(4) unsigned NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB; 
ALTER TABLE `metadata` ADD INDEX ( `datatype_id`), 
    ADD INDEX (`creation_date`); 
ALTER TABLE `team`  ADD INDEX ( `metadata_id`); 
ALTER TABLE `player` ADD INDEX `metadata_id` ( `metadata_id`, `team_id`), 
    ADD INDEX (`team_id`); 
ALTER TABLE `equipment` ADD INDEX `metadata_id` ( `metadata_id`, `team_id`), 
    ADD INDEX (`team_id`); 
ALTER TABLE `metadata` ADD CONSTRAINT `metadata_ibfk_1` FOREIGN KEY (`datatype_id`) REFERENCES `datatype` (`id`); 
ALTER TABLE `team`  ADD CONSTRAINT `team_ibfk_1`  FOREIGN KEY (`metadata_id`) REFERENCES `metadata` (`id`); 
ALTER TABLE `player` ADD CONSTRAINT `player_ibfk_1` FOREIGN KEY (`metadata_id`) REFERENCES `metadata` (`id`); 
ALTER TABLE `player` ADD CONSTRAINT `player_ibfk_2` FOREIGN KEY (`team_id`)  REFERENCES `team` (`id`); 
ALTER TABLE `equipment` ADD CONSTRAINT `equipment_ibfk_1` FOREIGN KEY (`metadata_id`) REFERENCES `metadata` (`id`); 
ALTER TABLE `equipment` ADD CONSTRAINT `equipment_ibfk_2` FOREIGN KEY (`team_id`)  REFERENCES `team` (`id`); 
INSERT INTO `datatype` VALUES(1,'team'),(2,'player'),(3,'equipment'); 

请注意,我知道我可以很容易做的播放器和设备用于给定小组ID 2个SELECTS的UNION加快这,但我使用本身不支持联盟的ORM和所以我宁愿尝试查看是否可以优化此查询。另外,我只是很好奇。

+0

哦,拜托,你可以用真正的baz替换你的baz-bar-foo foo吗? – markus 2009-08-12 21:52:40

+0

抱歉,我认为我失去了你,但我是否正确地猜测你的意思是用真正的表名替换foo,bar,baz? – mcsnolte 2009-08-12 21:54:29

+1

的确如此,因为正如你所看到的那样,这样很难遵循......就像使用变量名称foo和baz的代码......但是如果你想要获得你的baz,我就是吧! – markus 2009-08-12 21:57:44

回答

2

在MySQL中,很难优化“OR”条件。

一个常见的解决方法是将查询拆分为两个更简单的查询并使用UNION来组合它们。

(SELECT metadata.creation_date, datatype.name, player.id 
    FROM metadata 
    JOIN datatype ON datatype.id = metadata.datatype_id 
    JOIN player ON player.metadata_id = metadata.id 
    WHERE datatype.name = 'player' AND player.team_id = 1) 
UNION ALL 
(SELECT metadata.creation_date, datatype.name, equipment.id 
    FROM metadata 
    JOIN datatype ON datatype.id = metadata.datatype_id 
    JOIN equipment ON equipment.metadata_id = metadata.id 
    WHERE datatype.name = 'equipment' AND equipment.team_id = 1) 
ORDER BY creation_date; 

你必须使用括号,以便ORDER BY适用于UNION,而不是只对第二SELECT的结果的结果。


更新:你在做什么叫做多态关联,而且很难在SQL中使用。我甚至称它为SQL反模式,尽管有一些鼓励使用它的ORM框架。

在这种情况下,您真正​​拥有的是团队与玩家之间以及团队与设备之间的关系。球员不是装备和装备不是球员;他们没有共同的超类型。在OO意义上和关系意义上都是误导,你已经用这种方式来模拟它们。

我想说转储您的metadatadatatype表。这些是反关系结构。相反,使用team_id(我认为这是teams表的外键)。将玩家和装备视为不同的类型。如果您不能在您的ORM中使用UNION,请单独获取它们。然后将结果集合在您的应用程序中。

您不必在单个SQL查询中获取所有内容。

+0

感谢您的回应,但也许我应该在最后稍微提高一点。我已经发现了这一点,但出于好奇,我正在寻找一种方法来优化它,而不使用UNION。 – mcsnolte 2009-08-12 22:16:46

+0

啊,对不起,我错过了那个笔记。那么如果你对优化感兴趣,为什么在世界上你会使用ORM? :-P – 2009-08-12 22:20:57

+0

感谢您的解释并给出了这种类型的关系。我现在看到你在说什么,它大多是有道理的。 – mcsnolte 2009-08-14 15:49:50