优化MySQL查询

时间:2009-11-07 18:20:59

标签: optimization mysql query-optimization

我们有一个查询当前正在查杀我们的数据库,我知道必须有一种方法来优化它。我们有3个表:

  1. items - 项目表,其中每个项目都有关联的object_id,length,difficulty_rating,rating,avg_rating&状态
  2. lists - 列表表,基本上是用户创建的项目列表
  3. list_items - 包含2列的表:list_id,item_id

我们一直在使用以下查询来显示一个简单的HTML表格,该表格显示每个列表以及与列表相关的一些属性,包括所包含列表项的属性平均值:

select object_id, user_id, slug, title, description, items, 
       city, state, country, created, updated,
       (select AVG(rating) from items
          where object_id IN 
              (select object_id from list_items where list_id=lists.object_id) 
            AND status="A"
       ) as 'avg_rating',
       (select AVG(avg_rating) from items
          where object_id IN 
              (select object_id from list_items where list_id=lists.object_id) 
            AND status="A"
       ) as 'avg_avg_rating',
       (select AVG(length) from items 
          where object_id IN 
              (select object_id from list_items where list_id=lists.object_id) 
            AND status="A"
       ) as 'avg_length',
       (select AVG(difficulty_rating) from items 
          where object_id IN
              (select object_id from list_items where list_id=lists.object_id) 
            AND status="A"
       ) as 'avg_difficulty' 
    from lists
    where user_id=$user_id AND status="A" 
    order by $orderby LIMIT $start,$step

我们之所以没有在1个查询中解析这个以获取所有列表和后续查找以提取每个列表的平均值是因为我们希望用户能够对averages列进行排序(即'order' by avg_difficulty')。

希望我的解释是有道理的。必须有一个更有效的方法来做到这一点,我希望那里的MySQL大师可以指出我正确的方向。谢谢!

5 个答案:

答案 0 :(得分:6)

看起来你可以用连接替换所有子查询:

SELECT     l.object_id,
           l.user_id,
           <other columns from lists>
           AVG(i.rating) as avgrating,
           AVG(i.avg_rating) as avgavgrating,
           <other averages>
FROM       lists l
LEFT JOIN  list_items li 
ON         li.list_id = l.object_id
LEFT JOIN  items i 
ON         i.object_id = li.object_id
           AND i.status = 'A'
WHERE      l.user_id = $user_id AND l.status = 'A' 
GROUP BY   l.object_id, l.user_id, <other columns from lists>

这将为数据库引擎节省大量工作。

答案 1 :(得分:1)

这里是如何找到瓶颈的:

在SELECT之前添加关键字EXPLAIN。这将导致引擎输出SELECT的执行方式。

要了解有关使用此方法查询优化的详情,请参阅:http://dev.mysql.com/doc/refman/5.0/en/using-explain.html

答案 2 :(得分:1)

需要考虑的几件事情:

  1. 确保所有联接都在两侧编制索引。例如,您在几个地方加入 list_items.list_id = lists.object_id list_id object_id 都应该有索引。

  2. 您是否对平均值的变化进行了研究?您可能会受益于让工作线程(或cronjob)定期计算平均值,而不是每次运行此查询时都将负载放在RDBMS上。你需要将平均值存储在一个单独的表中...

  3. 此外,您使用状态作为枚举还是varchar?枚举的基数会低得多;如果状态列的值范围有限,请考虑切换到此类型。

  4. -AJ

答案 3 :(得分:0)

这是一个查询的地狱......你应该编辑你的问题并更改查询,使其更具可读性,尽管由于它的复杂性,我不确定是否可能。

无论如何,这里的简单答案是对数据库进行非规范化处理,并在索引的十进制列中缓存列表表本身的所有平均值。所有这些子查询都在扼杀你。

困难的部分,你必须弄清楚的是如何保持这些平均值更新。一种通常简单的方法是将所有项的计数和所有这些值的总和存储在两个单独的字段中。无论何时进行动作,都要将计数增加1,并将总和减去任何数量。然后更新表avg_field = sum_field / count_field。

答案 4 :(得分:0)

除了索引之外,即使是粗略的分析也表明你的查询包含了DBMS优化器无法发现的大量冗余(SQL是一种冗余语言,它承认了太多的等价,语法不同的表达式;这是一个已知的和记录的问题 - 参见例如SQL redundancy and DBMS performance,作者:Fabian Pascal)。

我将在下面重写您的查询,以突出显示:

让LI =

  select object_id from list_items where list_id=lists.object_id

  select object_id, user_id, slug, title, description, items, city, state, country, created, updated,
         (select AVG(rating)            from items where object_id IN LI AND status="A") as 'avg_rating',
         (select AVG(avg_rating)        from items where object_id IN LI AND status="A") as 'avg_avg_rating',
         (select AVG(length)            from items where object_id IN LI AND status="A") as 'avg_length',
         (select AVG(difficulty_rating) from items where object_id IN LI AND status="A") as 'avg_difficulty'
    from lists
   where user_id=$user_id AND status="A"
order by $orderby
   LIMIT $start, $step

注意:这只是重构这头野兽的第一步。

我想知道:为什么人们很少 - 如果有的话 - 使用views,甚至只是为了简化SQL查询?它将有助于编写更易于管理和可重构的查询。