2

我无法弄清楚为什么我的查询变慢了。它归结为四个表:团队、球员、设备和元数据。玩家和设备中的记录对团队有 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,所以我更愿意尝试看看如果我可以优化此查询。我也只是很好奇。

4

1 回答 1

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应用于 the 的结果,UNION而不是仅应用于 second 的结果SELECT


更新:您正在做的事情称为多态关联,并且很难在 SQL 中使用。我什至称它为 SQL 反模式,尽管一些 ORM 框架鼓励使用它。

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

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

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

于 2009-08-12T22:10:36.073 回答