2014-12-05 93 views
4

我有以下查詢:查詢與LEFT JOIN和ORDER BY ... LIMIT慢,使用文件排序

SELECT 
    fruit.date, 
    fruit.name, 
    fruit.reason, 
    fruit.id, 
    fruit.notes, 
    food.name 
FROM 
    fruit 
LEFT JOIN 
    food_fruits AS ff ON fruit.fruit_id = ff.fruit_id AND ff.type='fruit' 
LEFT JOIN 
    food USING (food_id) 
LEFT JOIN 
    fruits_sour AS fs ON fruits.id = fs.fruit_id 
WHERE 
    (fruit.date < DATE_SUB(NOW(), INTERVAL 180 DAY)) 
     AND (fruit.`status` = 'Rotten') 
     AND (fruit.location = 'USA') 
     AND (fruit.size = 'medium') 
     AND (fs.fruit_id IS NULL) 
ORDER BY `food.name` asc 
LIMIT 15 OFFSET 0 

和所有你所能想的指標,包括正在使用的情況如下:

fruit  - fruit_filter (size, status, location, date) 
food_fruits - food_type (type) 
food   - food (id) 
fruits_sour - fruit_id (fruit_id) 

我甚至有我本以爲這工作得更好,其沒有被使用的索引:

food_fruits - fruit_key (fruit_id, type) 
food   - id_name (food_id, name) 

的不幸的是,子句導致使用temporary表和filesort。沒有這個,查詢會運行分裂。我怎樣才能得到這個查詢不需要filesort?我錯過了什麼?

編輯:

的解釋: The Explain

+0

您可以在此查詢上運行EXPLAIN併發布輸出嗎? – Ashalynd 2014-12-05 23:14:44

+0

@Ashalynd是的,雖然格式可能有點奇怪。 – MirroredFate 2014-12-05 23:22:31

回答

1

這樣做的原因是你ORDER BY條款,是其沒有用於此查詢索引的一部分領域完成。引擎可以使用fruit_filter索引運行查詢,但是它必須在不同的字段上排序,這就是filesort進場時的情況(基本上意味着「不使用索引進行排序」,這要感謝評論中的提示)。

我不知道你作爲結果得到了多少次,但是如果差異很大,那麼我會創建一個具有中間結果的臨時表,然後對它進行排序。

(順便說一句,我不知道爲什麼你使用LEFT JOIN代替INNER JOIN爲什麼你使用food_fruits - 在評論中回答)

更新。

嘗試子查詢的方式,可能是(未經測試),其將來自預過濾排序:

SELECT 
    fr.date, 
    fr.name, 
    fr.reason, 
    fr.id, 
    fr.notes, 
    food.name 
FROM 
    (
    SELECT 
    fruit.date, 
    fruit.name, 
    fruit.reason, 
    fruit.id, 
    fruit.notes, 
    FROM 
    fruit 
    LEFT JOIN 
    fruits_sour AS fs ON fruit.id = fs.fruit_id 
    WHERE 
    (fruit.date < DATE_SUB(NOW(), INTERVAL 180 DAY)) 
     AND (fruit.`status` = 'Rotten') 
     AND (fruit.location = 'USA') 
     AND (fruit.size = 'medium') 
     AND (fs.fruit_id IS NULL) 
) as fr 
LEFT JOIN 
    food_fruits AS ff ON fr.fruit_id = ff.fruit_id AND ff.type='fruit' 
LEFT JOIN 
    food USING (food_id) 
ORDER BY `food.name` asc 
LIMIT 15 OFFSET 0 
+1

'food_fruits'是水果和食物之間的連接關聯表。我使用左連接,因爲我想要所有來自「水果」的行,但並非所有的「水果」都必須鏈接到「食物」。 – MirroredFate 2014-12-05 23:37:44

+0

子查詢似乎導致效率提高約7-8%。雖然這很好,並且我很欣賞它,但是如果能夠獲得更大的性能增益,那將會很不錯。我仍然不完全確定爲什麼'id_name'索引不能用於排序部分... – MirroredFate 2014-12-06 00:18:49

+1

小心,filesort並不意味着你的想法。 http://www.percona.com/blog/2009/03/05/what-does-using-filesort-mean-in-mysql/ – 2014-12-06 00:34:56

1

ORDER BY ... LIMIT條款需要一些排序,你知道的。優化性能的技巧是ORDER BY ... LIMIT最小的一組列,然後根據所選的十五行建立完整的結果集。所以讓我們嘗試一下子查詢中的一小組列。

 SELECT fruit.id, 
      food.name 
     FROM fruit 
    LEFT JOIN food_fruits AS ff ON fruit.fruit_id = ff.fruit_id 
           AND ff.type='fruit' 
    LEFT JOIN food USING (food_id) 
    LEFT JOIN fruits_sour AS fs ON fruits.id = fs.fruit_id 
     WHERE fruit.date < DATE_SUB(NOW(), INTERVAL 180 DAY) 
     AND fruit.`status` = 'Rotten' 
     AND fruit.location = 'USA' 
     AND fruit.size = 'medium' 
     AND fs.fruit_id IS NULL 
    ORDER BY food.name ASC 
     LIMIT 15 OFFSET 0 

此查詢爲您提供了15個頂級ID及其名稱。

我會將id添加到您現有的fruit_filter索引的末尾,以給出(size, status, location, date, id)。這將使其成爲compound covering index,並允許您的過濾查詢完全從索引中滿意。

除此之外,使用更多或不同的索引很難對其進行優化,因爲太多的查詢是由其他因素驅動的,例如您應用的LEFT JOIN ... IS NULL連接失敗標準。

然後你可以加入這個子查詢到你的水果表來拉取完整的結果集。

這一切都完成後,看起來像這樣。

SELECT fruit.date, 
     fruit.name, 
     fruit.reason, 
     fruit.id, 
     fruit.notes, 
     list.name 
    FROM fruit 
    JOIN (
       SELECT fruit.id, 
         food.name 
       FROM fruit 
      LEFT JOIN food_fruits AS ff ON fruit.fruit_id = ff.fruit_id 
              AND ff.type='fruit' 
      LEFT JOIN food USING (food_id) 
      LEFT JOIN fruits_sour AS fs ON fruits.id = fs.fruit_id 
       WHERE fruit.date < DATE_SUB(NOW(), INTERVAL 180 DAY) 
        AND fruit.`status` = 'Rotten' 
        AND fruit.location = 'USA' 
        AND fruit.size = 'medium' 
        AND fs.fruit_id IS NULL 
      ORDER BY food.name ASC 
       LIMIT 15 OFFSET 0 
     ) AS list ON fruit.id = list.id 
ORDER BY list.name 

你明白這是怎麼回事?在子查詢中,您可以找到足夠的數據來確定要檢索的行的哪一小部分。然後,將該子查詢加入主表以提取所有數據。限制排序內容中的行長度有助於提高性能,因爲MySQL可以將其排序緩衝區排序,而不必進行更復雜和更慢的排序/合併操作。 (但是,您無法從EXPLAIN中知道它是否會執行此操作。)

+0

我編寫了這個13小時後編寫它建議增加一個索引。 – 2014-12-06 14:06:42