[英]query with LEFT JOIN and ORDER BY…LIMIT slow, uses Filesort
我有以下查詢:
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)
不幸的是, ORDER BY
子句導致temporary
表和filesort
被使用。 沒有它,查詢運行lickety-split。 如何讓這個查詢不需要filesort
? 我錯過了什么?
編輯:
解釋:
原因是你的ORDER BY
子句在字段上完成,該子字段不是用於此查詢的索引的一部分。 引擎可以使用fruit_filter
索引運行查詢,但是它必須對不同的字段進行排序,這就是當filesort
進入游戲時(這基本上意味着“不使用索引排序”,這要歸功於評論中的提醒)。
我不知道你得到的結果是什么時候,但如果差別很大,那么我會創建一個包含中間結果的臨時表,然后對其進行排序。
(順便說一句,我不知道你為什么使用LEFT JOIN
而不是INNER JOIN
,為什么要使用food_fruits
- 在評論中回答)
UPDATE。
嘗試子查詢方法,可能是(未經測試),它將排序與預過濾分開:
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
你知道,你的ORDER BY ... LIMIT
子句需要一些排序。 優化性能的技巧是ORDER BY ... LIMIT
最小的列集,然后根據選擇的15行構建完整的結果集。 所以讓我們嘗試子查詢中的一組最小列。
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
此查詢為您提供十五個頂級ID及其名稱。
我會在現有的fruit_filter
索引的末尾添加id
來給出(size, status, location, date, id)
。 這將使其成為覆蓋索引的復合 ,並允許您的過濾查詢完全從索引中得到滿足。
除此之外,使用更多或不同的索引來優化它是很困難的,因為大部分查詢都是由其他因素驅動的,例如您已應用的LEFT JOIN ... IS NULL
join-fail准則。
然后,您可以將此子查詢加入到水果表中以獲取完整的結果集。
完成之后,這將是這樣的。
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它是否會這樣做。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.