Как я могу оптимизировать этот запрос MySQL, который включает два левых соединения?

StackOverflow https://stackoverflow.com/questions/1268924

Вопрос

Я не могу понять, почему мой запрос замедляется.Это сводится к четырем таблицам:команда, игрок, оборудование и метаданные.Записи об игроке и снаряжении имеют FK для команды, что делает команду родительской для игрока и снаряжения.И каждая из трех строк этих таблиц имеет запись в метаданных, в которой хранятся такие данные, как дата создания, идентификатор пользователя-создателя и т. д.

Я хотел бы получить сразу все записи об игроках и оборудовании, принадлежащие определенной команде, в порядке даты создания.Я начинаю с таблицы метаданных и слева присоединяюсь к таблицам игроков и оборудования через метаданные 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" Но когда я добавляю другого, чтобы получилось "...И (player.team_id = 1 ИЛИ 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');

Пожалуйста, обрати внимание Я понимаю, что мог бы легко ускорить этот процесс, выполнив UNION из двух SELECT для игрока и оборудования для заданного идентификатора команды, но ORM, который я использую, изначально не поддерживает UNION, и поэтому я бы предпочел попробовать и посмотреть, смогу ли я вместо этого можно оптимизировать этот запрос.А еще мне просто любопытно.

Это было полезно?

Решение

В 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-фреймворки поощряют его использование.

Что на самом деле имеет место в этом случае, так это отношения между командами и игроками, а также между командами и оборудованием.Игроки — это не снаряжение, а снаряжение — не игроки;у них нет общего супертипа.То, что вы смоделировали их таким образом, вводит в заблуждение как в объектно-ориентированном, так и в реляционном смысле.

Я бы сказал, выбрось свой metadata и datatype столы.Это антиреляционные структуры.Вместо этого используйте team_id (который, как я предполагаю, является внешним ключом для teams стол).Рассматривайте игроков и оборудование как отдельные типы.Получите их отдельно, если вы не можете использовать UNION в вашем ORM.Затем объедините наборы результатов в своем приложении.

Вам не обязательно получать все в одном запросе SQL.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top