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 ——profiler
说Copying 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:与案例一起使用时按子句分组不使用索引的主要内容,如果未能解决你的问题,请参考以下文章