我有以下查询:
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
?我错过了什么?
编辑:
解释:
答案 0 :(得分: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 :(得分:1)
您的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及其名称。
我会将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它是否会这样做。)