2009-08-12 160 views
2

我找不出爲什麼我的查詢變慢。它歸結爲四個表格:團隊,玩家,設備和元數據。玩家和裝備的記錄有一個FK隊,使球隊成爲球員和裝備的父母。並且這三個表中的每行都有一個元數據記錄,用於存儲創建日期,創建者用戶ID等內容。如何優化包含兩個左連接的MySQL查詢?

我想一次檢索所有玩家和設備記錄,屬於特定團隊,按創建日期排列。我從元數據表開始,通過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)「需要很長的時間。

以下是表和數據類型。請注意,有一件事情似乎有很大幫助,但不是那麼多,是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 2個SELECTS的UNION加快這,但我使用本身不支持聯盟的ORM和所以我寧願嘗試查看是否可以優化此查詢。另外,我只是很好奇。

+0

哦,拜託,你可以用真正的baz替換你的baz-bar-foo foo嗎? – markus 2009-08-12 21:52:40

+0

抱歉,我認爲我失去了你,但我是否正確地猜測你的意思是用真正的表名替換foo,bar,baz? – mcsnolte 2009-08-12 21:54:29

+1

的確如此,因爲正如你所看到的那樣,這樣很難遵循......就像使用變量名稱foo和baz的代碼......但是如果你想要獲得你的baz,我就是吧! – markus 2009-08-12 21:57:44

回答

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查詢中獲取所有內容。

+0

感謝您的迴應,但也許我應該在最後稍微提高一點。我已經發現了這一點,但出於好奇,我正在尋找一種方法來優化它,而不使用UNION。 – mcsnolte 2009-08-12 22:16:46

+0

啊,對不起,我錯過了那個筆記。那麼如果你對優化感興趣,爲什麼在世界上你會使用ORM? :-P – 2009-08-12 22:20:57

+0

感謝您的解釋並給出了這種類型的關係。我現在看到你在說什麼,它大多是有道理的。 – mcsnolte 2009-08-14 15:49:50