如果表包含使用MySQL的10000个条目,如何优化查询?

时间:2012-05-04 12:18:30

标签: mysql sql optimization query-optimization

当我像这样执行此查询时,由于user_fans表包含 10000 用户条目,因此执行时间过长。我该如何优化呢?

查询

SELECT uf.`user_name`,uf.`user_id`,
@post                := (SELECT COUNT(*) FROM post WHERE user_id = uf.`user_id`) AS post,
@post_comment_likes  := (SELECT COUNT(*) FROM post_comment_likes WHERE user_id = uf.`user_id`) AS post_comment_likes,
@post_comments       := (SELECT COUNT(*) FROM post_comments WHERE user_id = uf.`user_id`) AS post_comments,
@post_likes          := (SELECT COUNT(*) FROM post_likes WHERE user_id = uf.`user_id`) AS post_likes,

(@post+@post_comments) AS `sum_post`,
(@post_likes+@post_comment_likes) AS `sum_like`, 
((@post+@post_comments)*10) AS `post_cal`,      
((@post_likes+@post_comment_likes)*5) AS `like_cal`,
((@post*10)+(@post_comments*10)+(@post_likes*5)+(@post_comment_likes*5)) AS `total`  
FROM  `user_fans` uf  ORDER BY `total` DESC lIMIT 20

4 个答案:

答案 0 :(得分:7)

我会尝试通过在其他表上放置触发器来简化这个COMPLETELY,只需在User_Fans表中添加几个列...一个用于每个相应的count(),你试图得到...来自Posts,PostLikes ,PostComments,PostCommentLikes。

当记录被添加到任何一个表时,只需更新你的user_fans表以将1加到计数中......无论如何,它将基于用户的密钥ID实际上是瞬时的。至于" LIKES" ...类似,只有在某些事情被触发为" Like"的条件下,添加1 ..然后你的查询将是单一的直接数学记录并且不依赖于任何联接来计算"加权"总价值。随着您的表变得更大,查询也将变得更长,因为他们有更多的数据要倾注和聚合。您正在浏览每条user_fan记录,其实质上是查询来自所有其他表的每条记录。

所有这一切,保持你的桌子,我会重组如下......

SELECT 
      uf.user_name,
      uf.user_id,
      @pc := coalesce( PostSummary.PostCount, 000000 ) as PostCount,
      @pl := coalesce( PostLikes.LikesCount, 000000 ) as PostLikes,
      @cc := coalesce( CommentSummary.CommentsCount, 000000 ) as PostComments,
      @cl := coalesce( CommentLikes.LikesCount, 000000 ) as CommentLikes,
      @pc + @cc AS sum_post,
      @pl + @cl AS sum_like, 
      @pCalc := (@pc + @cc) * 10 AS post_cal,
      @lCalc := (@pl + @cl) * 5 AS like_cal,
      @pCalc + @lCalc AS `total`
   FROM
      ( select @pc := 0,
               @pl := 0,
               @cc := 0,
               @cl := 0,
               @pCalc := 0
               @lCalc := 0 ) sqlvars,
      user_fans uf
        LEFT JOIN ( select user_id, COUNT(*) as PostCount
                       from post
                       group by user_id ) as PostSummary
           ON uf.user_id = PostSummary.User_ID

        LEFT JOIN ( select user_id, COUNT(*) as LikesCount
                       from post_likes
                       group by user_id ) as PostLikes
           ON uf.user_id = PostLikes.User_ID

        LEFT JOIN ( select user_id, COUNT(*) as CommentsCount
                       from post_comment
                       group by user_id ) as CommentSummary
           ON uf.user_id = CommentSummary.User_ID

        LEFT JOIN ( select user_id, COUNT(*) as LikesCount
                       from post_comment_likes
                       group by user_id ) as CommentLikes
           ON uf.user_id = CommentLikes.User_ID

   ORDER BY 
      `total` DESC 
   LIMIT 20

My variables are abbreviated as 
"@pc" = PostCount
"@pl" = PostLikes
"@cc" = CommentCount
"@cl" = CommentLike
"@pCalc" = weighted calc of post and comment count * 10 weighted value
"@lCalc" = weighted calc of post and comment likes * 5 weighted value

预先查询的LEFT JOIN运行那些查询ONCE,然后整个事物被连接而不是作为每个记录的子查询被命中。通过使用COALESCE(),如果LEFT JOINed表结果中没有这样的条目,你就不会被NULL值搞乱,因此我将它们默认为000000。

澄清您的问题

您可以将任何QUERY作为" AS AliasResult"。 " As"也可用于简化任何长表名称,以简化可读性。别名也可以使用相同的表,但作为不同的别名来获得类似的内容,但出于不同的目的。

select
      MyAlias.SomeField
   from
      MySuperLongTableNameInDatabase MyAlias ...

select
      c.LastName,
      o.OrderAmount
   from
      customers c
         join orders o
            on c.customerID = o.customerID  ...

select
      PQ.SomeKey
   from
      ( select ST.SomeKey
           from SomeTable ST
           where ST.SomeDate between X and Y ) as PQ
         JOIN SomeOtherTable SOT
            on PQ.SomeKey = SOT.SomeKey ...

现在,上面的第三个查询不实用,需要(完整查询产生别名" PQ"表示" PreQuery")。如果您想要预先限制某些其他复杂条件并希望在为所有最终结果执行多个其他表的额外连接之前需要更小的集合,则可以执行此操作。

自" FROM"不必是一个实际的表,但可以是查询本身,查询中使用的任何其他位置,它必须知道如何引用此预查询结果集。

此外,在查询字段时,它们也可以是"作为FinalColumnName"将结果简化为可以使用的地方。

选择     CONCAT(User.Salutation,User.LastName)作为CourtesyName   来自......

选择       Order.NonTaxable     + Order.Taxable     +(Order.Taxable * Order.SalesTaxRate)为OrderTotalWithTax    来自......

" As" columnName不是聚合所必需的,但最常见的方式就是这样。

现在,关于MySQL变量......如果你正在进行存储过程,很多人会预先声明它们在剩下的程序之前设置它们的默认值。您可以通过设置并将结果赋予"别名"来在查询中在线执行它们。参考。在执行这些变量时,select将模拟始终返回值为SINGLE RECORD的值。它几乎就像在查询中使用的可更新的单个记录。您不需要应用任何特定的"加入"条件,因为它可能对查询中的其余表没有任何影响......本质上,创建一个笛卡尔结果,但是对任何其他表的一条记录永远不会创建重复,所以下游没有损坏。

select 
       ...
   from 
      ( select @SomeVar := 0,
               @SomeDate := curdate(),
               @SomeString := "hello" ) as SQLVars

现在,sqlvars如何工作。想想一个线性程序......一个命令在查询运行时按照确切的顺序执行。然后将该值重新存储回" SQLVars"准备下次通过。但是,您不能将其引用为SQLVars.SomeVar或SQLVars.SomeDate ...只是@SomeVar:= someNewValue。现在,当在查询中使用@var时,它也存储为" As ColumnName"在结果集中。有时,这可能只是在准备下一条记录时的占位符计算值。然后,每个值可直接用于下一行。所以,给出以下样本......

select
      @SomeVar := SomeVar * 2 as FirstVal,
      @SomeVar := SomeVar * 2 as SecondVal,
      @SomeVar := SomeVar * 2 as ThirdVal
   from
      ( select @SomeVar := 1 ) sqlvars,
      AnotherTable
   limit 3

Will result in 3 records with the values of 

FirstVal    SecondVal   ThirdVal
2           4           8
16          32          64
128         256         512

注意@SomeVar的值是如何使用的,因为每列使用它...所以即使在同一条记录上,更新后的值也可立即用于下一列......也就是说,现在看看尝试构建一个每个客户的模拟记录数/排名...

select
      o.CustomerID,
      o.OrderID
      @SeqNo := if( @LastID = o.CustomerID, @SeqNo +1, 1 ) as CustomerSequence,
      @LastID := o.CustomerID as PlaceHolderToSaveForNextRecordCompare
   from
      orders o,
      ( select @SeqNo := 0, @LastID := 0 ) sqlvars
   order by
      o.CustomerID

" Order By"子句强制首先按顺序返回结果。因此,在这里,返回每个客户的记录。第一次通过,LastID为0,客户ID为... 5。由于不同,它返回1作为@SeqNo,然后它将该客户ID保存到下一条记录的@LastID字段中。现在,客户的下一条记录...最后一个ID是相同的,所以需要@SeqNo(现在= 1),并且为同一客户添加1比1并成为#2 ...继续路径..

至于更好地编写查询,请查看MySQL标记并查看一些重要的贡献者。查看问题和一些复杂的答案以及解决问题的方法。并不是说没有其他声誉得分较低的人刚刚开始并且完全胜任,但你会发现谁给出了很好的答案以及为什么会这样做。看看他们发布的答案的历史。您阅读和遵循的内容越多,您就越能更好地处理更复杂的查询。

答案 1 :(得分:1)

  1. 您可以将此查询转换为Group By子句,而不是为每列使用Subquery。
  2. 您可以在关系参数上创建索引(这将是优化查询响应的最有用方式)。

答案 2 :(得分:1)

1000个用户记录根本没有太多数据。

您可以对数据库本身进行一些工作:

1)您是否在外键上设置了相关索引(在每个表中的user_id上设置了索引)?尝试在查询http://www.slideshare.net/phpcodemonkey/mysql-explain-explained

之前运行EXPLAIN

2)您的数据类型是否正确?

答案 3 :(得分:1)

查看@me(参见图像1 )和@DRapp(参见图像2 )之间的区别查询执行时间并解释。当我读@Drapp的答案时,我意识到我在这个查询中做错了什么以及为什么我的查询花了这么多时间基本上答案是如此简单我的查询依赖于子查询或@Drapp使用派生(临时/文件排序)的帮助会话变量,别名和联接......

  

图像1执行时间(00:02:56:321

enter image description here

  

图像2执行时间(00:00:32:860

enter image description here