Как я могу оптимизировать этот запрос MySQL, который включает два левых соединения?
-
13-09-2019 - |
Вопрос
Я не могу понять, почему мой запрос замедляется.Это сводится к четырем таблицам:команда, игрок, оборудование и метаданные.Записи об игроке и снаряжении имеют 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.