【问题标题】:How to optimize query if table contain 10000 entries using MySQL?如果表包含使用 MySQL 的 10000 个条目,如何优化查询?
【发布时间】:2012-05-13 23:45:52
【问题描述】:

当我像这样执行此查询时,它们会花费大量执行时间,因为 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

【问题讨论】:

    标签: mysql sql optimization query-optimization


    【解决方案1】:

    我会尝试通过在您的其他表上放置触发器来完全简化这一点,并且只需在您的 User_Fans 表中添加几列......您尝试从 Posts、PostLikes 获取的每个 count() 对应一个,发表评论,发表评论喜欢。

    当一条记录添加到任何表时,只需更新您的 user_fans 表以将计数加 1...无论如何,这将基于用户的密钥 ID 几乎是即时的。至于“LIKES”...类似,只有在某些东西被触发为“Like”的情况下,加1..然后您的查询将是对单个记录的直接数学运算,而不依赖于任何连接来计算a “加权”总值。随着您的表变得更大,查询也将变得更长,因为它们有更多的数据要涌入和聚合。您正在浏览每条 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 运行这些查询一次,然后整个事物被连接而不是作为每个记录的子查询被命中。通过使用 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”不必是一个实际的表,但它本身可以是一个查询,查询中使用的任何其他地方,它必须知道如何引用这个预查询结果集。

    此外,在查询字段时,它们也可以是“As FinalColumnName”,以将结果简化到它们将被使用的位置。

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

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

    “As”columnName 不一定是聚合,但最常见的是这种方式。

    现在,关于 MySQL 变量...如果您正在执行存储过程,许多人会在执行其余过程之前预先声明它们设置默认值。您可以通过设置并为结果提供“别名”引用来在查询中内联执行它们。执行这些变量时,选择将模拟始终返回一个 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 标签并查看一些重要的贡献者。研究问题和一些复杂的答案以及解决问题的工作原理。并不是说没有其他声誉得分较低的人刚刚起步并且完全胜任,但是您会发现谁给出了好的答案以及为什么。看看他们发布的答案历史。您阅读和关注的越多,您就越能更好地编写更复杂的查询。

    【讨论】:

    • 感谢您与我分享您的经验...如果您愿意,我有很多关于此查询的问题,请回答我的问题@DRapp
    • 别名如何在没有聚合函数的情况下工作 (as PostSummary ON uf.user_id = PostSummary.User_ID) 以及如何找到他的别名列名 user_id
    • 这个查询是如何运行的,你能详细说明一下吗?我是MySql领域的新手,我想学习这个@DRapp你能帮我吗
    • 如果您没有时间详细说明此查询,请给我指导如何改进和学习 mysql 优化
    • @QueryMaster,澄清的修订答案。
    【解决方案2】:
    1. 您可以将此查询转换为 Group By 子句,而不是对每列使用子查询。
    2. 您可以在关系参数上创建索引(这将是优化查询响应的最有用的方法)。

    【讨论】:

      【解决方案3】:

      1000 条用户记录根本算不上多少数据。

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

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

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

      【讨论】:

        【解决方案4】:

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

        图像 1 exe 时间 (00:02:56:321)

        image 2 exe 时间 (00:00:32:860)

        【讨论】:

          猜你喜欢
          • 2011-02-11
          • 1970-01-01
          • 2013-05-12
          • 1970-01-01
          • 1970-01-01
          • 2011-06-27
          • 2010-11-15
          • 1970-01-01
          相关资源
          最近更新 更多