MySQL:与案例一起使用时按子句分组不使用索引

Posted

技术标签:

【中文标题】MySQL:与案例一起使用时按子句分组不使用索引【英文标题】:MySQL : Group By Clause Not Using Index when used with Case 【发布时间】:2016-05-03 10:24:03 【问题描述】:

我正在使用 mysql

我无法更改数据库结构,所以遗憾的是,这不是一个选项

问题:

当我将 GROUP BY 与 CASE 一起使用时(根据我的情况),MYSQL 使用 file_sort 并且延迟很长(大约 2-3 分钟):

http://sqlfiddle.com/#!9/f97d8/11/0

但是当我不使用 CASE 只是 GROUP BY group_id 时,MYSQL 很容易使用 索引和结果很快:

http://sqlfiddle.com/#!9/f97d8/12/0

场景:详细

msgs,包含已发送消息的记录,字段:

身份证,

user_id,(发送消息的人)

type, (0=> 表示它是组 msg。在此下发送的所有 msg 都由 group_id 标记。所以假设 group_id = 5 发送了 5 个 msgs,该表将有 5 条 group_id =5 和类型的记录=0。对于type>0,group_id 将为NULL,因为所有其他类型都没有group_id,因为它们是发送给单个收件人的单独消息)

group_id(如果 type=0,将包含 group_id,否则为 NULL)

表包含大约 1000 万条用户 id 50001 和不同类型(即组和个人消息)的记录

现在是查询:

SELECT 
  msgs.*
FROM
  msgs 
  INNER JOIN accounts 
    ON (
      msgs.user_id = accounts.id
    ) 
WHERE 1 
  AND msgs.user_id IN (50111) 
  AND msgs.type IN (0, 1, 5, 7) 
GROUP BY CASE `msgs`.`type` WHEN 0 THEN `msgs`.`group_id` ELSE `msgs`.`id` END 
ORDER BY `msgs`.`group_id` DESC
LIMIT 100

必须在一个 QUERY 中获取摘要,

所以发送到组的消息让我们说 5(此表中有 5 条记录)将显示为 1 条记录以进行摘要(我可能稍后显示 COUNT,但这不是问题)。

单个 msgs 的 group_id 为 NULL,所以我不能只输入 'GROUP BY group_id' 因为这会将所有单个 msgs 分组到单个记录,这是不可接受的。

示例输出可以是:

id    owner_id,    type    group_id    COUNT   
1       50001       0       2           5    
1       50001       1       NULL        1    
1       50001       4       NULL        1    
1       50001       0       7           5
1       50001       5       NULL        1
1       50001       5       NULL        1
1       50001       5       NULL        1
1       50001       0       10          5

现在的问题是使用 CASE 后的 GROUP 条件(我目前认为我必须这样做,因为如果 type=0,我只需要按 group_id 分组)导致很多延迟,因为它没有使用索引,如果我不使用 CASE(就像按 group_id 分组)。请查看上面的 SQLFiddles 以查看解释结果

谁能给点建议如何优化它

更新

我尝试了一种解决方法,它确实可以解决(将初始查询降低到 1 秒)。使用联合,它的作用是通过联合最小化结果集,强制 SQL 写入磁盘以进行文件排序(由于结果集巨大),限制组 msgs 和单个 msgs 的结果集(查看下面的查询)

-- union 的第一部分检索组 msgs(类型为 0 并且需要按 group_id 分组)。应用限制来吸引失控的结果集

-- 第二个查询检索单个 msgs,(类型为 !=0 的那些,按 msgs.id 分组 - 不是必需的,只是为了避免由于连接而导致的重复条目)。应用限制来吸引失控的结果集

-- 将两者连接起来以检索所需的结果集

这是查询:

SELECT 
  * 
FROM
  (
    (
      SELECT 
      msgs.id as reference_id, user_id, type, group_id
    FROM
      msgs 
      INNER JOIN accounts 
        ON (msgs.user_id = accounts.id) 
     WHERE 1 
       AND accounts.id IN (50111 ) AND type = 0
      GROUP BY msgs.group_id 
      ORDER BY msgs.id  DESC
      LIMIT 40
     ) 
    UNION
    ALL 
    (
      SELECT 
      msgs.id as reference_id, user_id, type, group_id
      FROM
      msgs 
      INNER JOIN accounts 
        ON (
          msgs.user_id = accounts.id
        ) 
    WHERE 1 
      AND msgs.type != 0
      AND accounts.id IN (50111) 
    GROUP BY msgs.id 
    ORDER BY msgs.id 
    LIMIT 40
    )
  ) AS temp 
ORDER BY reference_id 
LIMIT 20,20

但有很多警告,

-我还需要处理内部查询的限制。假设每页 20recs,我在第 4 页。对于内部查询,我需要应用限制 0,80,因为我不确定这两个部分中的哪一个在前 3 页中有多少记录。因此,随着每页记录和页数的增长,我的查询变得越来越重。假设每页 1k 记录,而我在第 100 页或 1K 上,负载变得越来越重,时间呈指数增长

我需要在内部查询中处理排序,然后应用于 union 准备的结果集,条件需要分别应用于两个内部查询(但问题不大)

-不能使用 calc_found_rows,所以需要单独使用查询来获取计数

主要问题是第一个问题。分页越高,越重

【问题讨论】:

在没有任何样本输入的情况下,我发现样本输出非常令人痛苦。 此表中没有user_id。如果您愿意,请考虑遵循这个简单的两步操作: 1. 如果您还没有这样做,请提供适当的 CREATE 和 INSERT 语句(和/或 sqlfiddle),以便我们可以更轻松地复制问题。您不需要提供 5m INSERTS。十二通常足以具有代表性。 2. 如果您尚未这样做,请提供与步骤 1 中提供的信息相对应的所需结果集。 @Strawberry 抱歉耽搁了,已经完成了: 还是一团糟。 汗先生,请阅读此内容。 meta.***.com/questions/271055/… 此外,您正在以非标准方式使用 GROUP BY 工具,因此很难理解您的意图。请阅读这个。 dev.mysql.com/doc/refman/5.7/en/group-by-handling.html 此外,如果您订购聚合,您将始终拥有文件排序。在EXPLAIN 输出中看到它可能会让您感到困惑。 percona.com/blog/2009/03/05/… 【参考方案1】:

这会跑得更快吗?

SELECT  id, user_id, type, group_id
    FROM  
      ( SELECT  id, user_id, type, group_id, IFNULL(group_id, id) AS foo
            FROM  msgs
            WHERE  user_id IN (50111)
              AND  type IN (0, 1, 5, 7) 
      )
    GROUP BY  foo
    ORDER BY  `group_id` DESC
    LIMIT  100 

它需要INDEX(user_id, type)

这是否给出了“正确”的答案?

SELECT  DISTINCT *
    FROM  msgs
    WHERE  user_id IN (50111)
      AND  type IN (0, 1, 5, 7)
    GROUP BY  IFNULL(group_id, id)
    ORDER BY  `group_id` DESC
    LIMIT  100

(需要相同的索引)

【讨论】:

虽然没有任何重大影响。遗憾的是。在当前负载下,我发布的初始查询需要约 37 秒,您的第一个查询需要 31 秒(更好,但我需要根据要求将其降低到 max5 ——profilerCopying to tmp 花了 29.3 秒31,有道理),您的第二个查询花费了约 47 秒(根据分析器,25 秒发送数据,19.5 用于复制到临时)。我确实创建了 user_id_type 索引,它确实选择了它作为键,但没有使用索引 acc。到解释中的额外信息 另外,w.r.t 你的第一个查询,当最终分组和排序时,派生表有数百万个可能的结果,我们可以对这个巨大的磁盘 i/o 和文件排序做些什么吗?每个场景都在引导我进行文件排序,这是这里的主要问题 添加我的索引后请提供SHOW CREATE TABLE。并提供EXPLAIN SELECT ... 这里是解释的链接。sqlfiddle.com/#!9/befc4/5/0。您可以在左侧窗格中查看 show create 瑞克,我已经用替代解决方案更新了原始帖子,这还不够好。花点时间阅读第一篇文章中的更新部分,以查看解决方法及其拥有的注意事项

以上是关于MySQL:与案例一起使用时按子句分组不使用索引的主要内容,如果未能解决你的问题,请参考以下文章

06MySQL分页查询子查询经典案例联合查询

如何创建mysql索引以及索引的优缺点

MySQL的语法高级之SELECT

mysql基础查询语法

MySQL数据库查询

mysql查询子查询连接查询