如何优化涉及两个左连接的MySQL查询?

时间:2009-08-12 21:46:00

标签: optimization mysql left-join polymorphic-associations

我无法弄清楚为什么我的查询会变慢。它归结为四个表:团队,玩家,设备和元数据。玩家和装备中的记录拥有FK团队,使团队成为玩家和设备的父母。并且所有这三个表的行都有一个元数据记录,存储创建日期,创建者用户ID等内容。

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

以下是查询:

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做两个SELECTS的UNION来轻松加快速度,但是我使用的ORM本身并不支持UNION的所以我宁愿尝试看看我是否可以优化这个查询。我也很好奇。

1 个答案:

答案 0 :(得分: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查询中获取所有内容。