【问题标题】:How can I optimize this MySQL query that involves two left joins?如何优化这个涉及两个左连接的 MySQL 查询?
【发布时间】:2010-11-19 03:05:20
【问题描述】:

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

我想一次性检索所有属于特定球队的球员和装备记录,按创建日期顺序排列。我从元数据表开始,通过 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)" 这需要很多很多时间。

这里是表格和数据类型。请注意,似乎有很大帮助的一件事是,元数据_id 和团队_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所以我更愿意尝试看看我是否可以优化这个查询。我也只是好奇。

【问题讨论】:

  • 拜托,你能把你的 baz-bar-foo foo 换成真正的 baz 吗?
  • 对不起,我想我失去了你,但我猜对了,你的意思是用真实的表名替换 foo、bar、baz?
  • 没错,因为正如你所看到的,很难像这样理解......就像使用变量名 foo 和 baz 的代码......但如果你想 foo 你的 baz,我吧!
  • 好的,现在玩家和装备都属于一个团队了。

标签: optimization mysql left-join polymorphic-associations


【解决方案1】:

在 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 查询中获取所有内容。

【讨论】:

  • 感谢您的回复,但也许我应该把我的笔记放在最后一点。我已经发现了这一点,但出于好奇,我正在寻找一种不使用 UNION 来优化它的方法。
  • 啊,对不起,我错过了那张纸条。好吧,如果您对优化感兴趣,那么您到底为什么要使用 ORM? :-P
  • 感谢您的解释并为此类关系提供术语。我现在明白你在说什么,而且大部分都说得通。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-04
  • 2011-12-05
  • 2015-03-18
  • 1970-01-01
相关资源
最近更新 更多