【问题标题】:MySQL query is too slow, how can I improve this?MySQL查询太慢,我该如何改善?
【发布时间】:2016-09-22 22:33:49
【问题描述】:

我有这个查询,在大约 300.000 行的表上提取数据大约需要 14 秒。 该表将在不久的将来增加其大小......超过一百万行。 我使用了EXISTS 子句而不是IN 子句,并进行了改进。 但是查询太慢了。 你有什么解决办法吗? 提前致谢。

这是查询:

SELECT 
    flow,
    COUNT(*) tot
FROM
    (
        SELECT 
            ff.session_id,
            GROUP_CONCAT(ff.page, '#', ff.snippet_params,'$',ff.is_lead SEPARATOR '|') flow 
            FROM table_a ff
            WHERE EXISTS 
                (
                    SELECT
                        f.session_id
                    FROM table_a f
                    WHERE f.session_id = ff.session_id
                    AND f.is_lead = 1
                    GROUP BY f.user_id 
                    ORDER BY f.user_id, f.`timestamp` 
                )
            GROUP BY ff.user_id 
            ORDER BY ff.user_id, ff.`timestamp`, ff.session_id 
    )
AS flow
GROUP BY flow 
ORDER BY tot DESC LIMIT 10

这是解释:

id  select_type         table       type    possible_keys       key         key_len  ref                              rows  Extra                                         
------  ------------------  ----------  ------  ------------------  ----------  -------  -----------------------------  ------  ----------------------------------------------
 1  PRIMARY             <derived2>  ALL     (NULL)              (NULL)      (NULL)   (NULL)                            532  Using temporary; Using filesort               
 2  DERIVED             ff          ALL     (NULL)              (NULL)      (NULL)   (NULL)                         322154  Using temporary; Using filesort               
 3  DEPENDENT SUBQUERY  f           ref     is_lead,session_id  session_id  767      ff.session_id       3  Using where; Using temporary; Using filesort  

【问题讨论】:

    标签: mysql sql


    【解决方案1】:

    ORDER BY 中的额外表达式没有任何意义,因为“GROUP BY user_id”将保证user_id 的唯一值。

    ORDER BY 操作在GROUP BY 操作之后应用。如果我的意图是为每个user_id 获得最低的session_id,我会使用MIN 聚合。在原始查询中,ORDER BY 对返回的 session_id 没有任何影响。 session_id 返回的值是不确定的。

    (其他数据库会在此查询中抛出错误。针对 GROUP BY 的 MySQL 特定扩展允许查询运行,但我们可以通过在 sql_mode 中包含 ONLY_FULL_GROUP_BY 来获得更标准的行为。)

    EXISTS 子查询中的GROUP BY 没有任何意义。如果找到行,则存在行。无需执行 GROUP BY 并聚合找到的行。

    更仔细地观察,似乎没有必要在 SELECT 列表中返回 session_id。 (在flow 视图查询中,或在 EXISTS 子查询中。)

    如果我们删除无关的语法并将查询缩减到其本质,到真正重要的部分,我们会得到一个如下所示的查询:

     SELECT flow.flow  AS flow
          , COUNT(*)   AS tot
       FROM (
              SELECT GROUP_CONCAT(ff.page,'#',ff.snippet_params,'$',ff.is_lead SEPARATOR '|') AS flow
                FROM table_a ff
               WHERE EXISTS
                     ( SELECT 1
                         FROM table_a f
                        WHERE f.is_lead = 1
                          AND f.session_id = ff.session_id
                     )
               GROUP BY ff.user_id
            ) flow
      GROUP BY flow.flow
      ORDER BY tot DESC
      LIMIT 10
    

    查询基本上说要从(不幸命名的表)table_a 中获取所有行,这些行有一个 session_id,它与 table_a 中的至少一行匹配,session_id 的值相同,它也有 @987654339 @ 值为 1。

    然后取出所有找到的行,并根据user_id 列中的值聚合它们。

    很奇怪,GROUP_CONCAT 中没有 ORDER BY,没有 DISTINCT 关键字也有点奇怪。

    对于 GROUP_CONCAT 聚合返回不确定的行顺序并且还可能包含重复值是很奇怪的。 (假设外部查询将基于从该 GROUP_CONCAT 聚合返回的值执行另一个聚合。)

    但是,我不确定这个查询应该回答什么问题。而且我不知道什么是独特的,什么不是。

    我们知道 EXISTS 子查询可以重写为 JOIN 操作:

     SELECT flow.flow  AS flow
          , COUNT(*)   AS tot
       FROM (
              SELECT GROUP_CONCAT(ff.page,'#',ff.snippet_params,'$',ff.is_lead SEPARATOR '|') AS flow
                FROM ( SELECT d.session_id
                         FROM table_a d
                        WHERE d.is_lead = 1
                        GROUP BY d.session_id
                     ) e
                JOIN table_a ff
                  ON ff.session_id = e.session_id
               GROUP BY ff.user_id
            ) flow
      GROUP BY flow.flow
      ORDER BY tot DESC
      LIMIT 10
    

    我们可以努力使查询运行得更快。但在我这样做之前,我想确保查询返回的集合与规范匹配。我需要确保查询实际上回答了它旨在回答的问题。

    我怀疑原始查询不正确。也就是说,我认为如果查询返回“正确”的结果,它是偶然的,而不是因为它被保证。或者是因为表中行的唯一性(基数)有些特殊,或者是由于处理行的意外顺序。

    在我花时间调整查询和添加索引之前,我想确保查询能够返回正确的结果。


    问:为什么GROUP_CONCAT 中没有ORDER BY?例如

     GROUP_CONCAT( foo ORDER BY something)
    

    问:没有 DISTINCT 关键字是否有特定原因?

     GROUP_CONCAT(DISTINCT foo ORDER BY something)
    

    问:我们是否应该关注 GROUP_CONCAT 可能(静默)返回截断值? (基于group_concat_max_length变量的设置?)


    跟进

    为了获得上述答案中最后一个查询的最佳性能,我建议添加以下索引:

     ... ON table_a (session_id, is_lead, page, snippet_params) 
    

    或任何类似的索引,以session_idis_lead 作为前导列(按此顺序),还包括pagesnippet_params 列。如果将 ORDER BY 添加到 GROUP_CONCAT,我们可能需要稍微不同的索引。

    对于外部查询,无法绕过派生的flow 列的“使用文件排序”操作。 (除非您正在运行更新版本的 MySQL,可能会在其中创建索引。或者我们愿意将查询分成两个单独的操作。一个查询将内联视图具体化为表,第二个查询要运行反对。)

    【讨论】:

    • 我读了你的答案,但我需要时间来研究它。明天我可以花一些时间来回答你。 Ps:您的查询得到与我相同的结果......但您花了〜1秒。谢谢现在,明天我检查并回答你。
    • 我的回答中的查询应该返回一个等效的结果。但我对查询非常怀疑......尤其是 GROUP_CONCAT 返回的值是不确定的。也就是说,MySQL 可以返回'2#b$y|1#a$x' 代替'1#a$x|2#b$y',其中任何一个都可以满足要求。查询中没有任何内容指定应返回哪些值。 MySQL 也可以免费返回。
    【解决方案2】:

    在这个子查询中,您使用的是 group by,但您没有聚合功能。

    用于检查 EXIST 的结果为 f.session_id 基于 group by 与否是一样的 .. 你也应该删除 group by 和 order by

           WHERE EXISTS 
                (
                    SELECT
                        f.session_id
                    FROM table_a f
                    WHERE f.session_id = ff.session_id
                    AND f.is_lead = 1
                    GROUP BY f.user_id 
                    ORDER BY f.user_id, f.`timestamp` 
                )
    

    这边

              WHERE EXISTS 
                (
                    SELECT
                        f.session_id
                    FROM table_a f
                    WHERE f.session_id = ff.session_id
                    AND f.is_lead = 1
                )
    

    查看您的查询,我认为可以重构,例如:

    SELECT flow ,  COUNT(*) tot
    FROM (
                select 
                GROUP_CONCAT(ff.page, '#', ff.snippet_params,'$',ff.is_lead SEPARATOR '|') flow ,
    
      FROM table_a ff
      WHERE  f.is_lead = 1
                GROUP BY ff.user_id ) as new_flow
    
    GROUP BY  flow
    ORDER BY tot DESC LIMIT 10
    

    【讨论】:

    • 我采纳了你的建议,现在执行大约需要 11 秒!谢谢!不幸的是,查询也很慢;)
    • 也许我必须更改数据库引擎?可以是一个选项吗?
    • 3 秒比什么都没有好 .. 但是 .. 无论如何你在 session_id 上有正确的索引吗?
    • 您使用的是哪个数据库引擎?
    • 我在 session_id 上有索引,我使用 MySQL 5.5
    【解决方案3】:

    您需要确保 f.session_id 和 f.is_lead 已编入索引。它目前正在针对 table_a 的 ff 引用对中间结果中的每一行进行 f 表扫描。

    【讨论】:

      【解决方案4】:
      • 去掉 count(*),如果函数存在,IIRC MySQL 不能再缓存查询,尝试另一种方法。
      • 去掉子查询,IIRC MySQL 也不能缓存子查询。

      很难给出这个查询(或这些查询)的优化版本。你可能不想改变你的数据库结构,所以它允许更简单的查询。也许其他值的一些缓存(redis等)......

      【讨论】:

      • 嗨,谢谢。但是这个表是一个“日志表”,我可以规范化,但是我必须使用一些表连接来获得结果......我认为这不是一个改进......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-27
      • 2010-10-10
      • 1970-01-01
      • 2022-01-04
      • 2022-01-02
      • 1970-01-01
      相关资源
      最近更新 更多