2014-12-05 92 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