MySQL查询慢不知道如何优化

Posted

技术标签:

【中文标题】MySQL查询慢不知道如何优化【英文标题】:MySQL query SLOW Don't know how to optimize 【发布时间】:2020-03-09 14:28:26 【问题描述】:

mysql 查询 SLOW 不知道如何优化

我认为我对硬件 60GB RAM 10 核 SSD 没问题

您好我有一个大问题,这个查询在 Mysql 上运行缓慢,他们的查询如下:

# Thread_id: 1165100  Schema: back-Alvo-11-07-19  QC_hit: No
# Query_time: 9.015205  Lock_time: 0.000188  Rows_sent: 1  Rows_examined: 2616880
# Rows_affected: 0
SET timestamp=1568549358;
SELECT count(*) as total_rows FROM(
(SELECT m.*
FROM phpfox_channel_video AS m
INNER JOIN phpfox_channel_category AS mc
    ON(mc.category_id = mc.category_id)
INNER JOIN phpfox_channel_category_data AS mcd
    ON(mcd.video_id = m.video_id)

WHERE m.in_process = 0 AND m.view_id = 0 
     AND m.module_id = 'videochannel' 
    AND m.item_id = 0 AND m.privacy IN(0) 
    AND mcd.category_id = 17
GROUP BY m.video_id
ORDER BY m.time_stamp DESC
LIMIT 12

)) AS m
JOIN phpfox_user AS u
    ON(u.user_id = m.user_id);

这个查询运行很慢,你可以看到 9 秒

在寻找优化查询的在线帮助时,总是谈论添加索引,

正如您在下面看到的 EXPLAIN 语句我已经有索引

你们有什么想法我应该在哪里提高这个查询的速度吗?我不是 DB 家伙,对此很难。这是一个网站,有 400,000 个视频。

谢谢

【问题讨论】:

"MySQL 查询慢,不知道如何优化" 如果没有SHOW CREATE TABLE <table_name> foreach 表和EXPLAIN <query> 输出,我们也没有任何想法。. 您的图片难以阅读,请尝试添加文字版本或更好的图片 另外SELECT m.* ... GROUP BY m.video_id ORDER BY m.time_stamp DESC 可能使用错误,请查看手册在使用GROUP BY 时MySQL 如何处理functional dependency 的检测并检查您是否正确使用它.. 正确我的意思是至少运行MySQL 5.7.5+ 和一个正在运行的ONLY_FULL_GROUP_BY sql_mode 否则查询无法生成可信赖的结果.. 【参考方案1】:

说明显示您没有使用表phpfox_channel_video as m 上的索引,并且它正在使用表phpfox_channel_category AS mc 上的临时索引,这意味着它没有使用索引,而是首先构建索引,这需要相当长的时间。

另外,表phpfox_channel_category_data AS mcd 的索引可能会更好。

你需要的索引是:

CREATE INDEX idx_cat_data_video_id ON phpfox_channel_category_data
  (category_id, video_id);
CREATE INDEX idx_channel_cat_id ON phpfox_channel_category (category_id);
CREATE INDEX idx_video_mult ON phpfox_channel_video
  (in_process, view_id, module_id, item_id, privacy, video_id, time_stamp);

【讨论】:

嗨,Marlin,你能检查一下这个吗? 相同的索引也应该适用于该查询。您无法为LIKE 表达式编制索引。 谢谢 Marlin 我已经应用了你的答案,没有看到性能有太大改善不知道再去哪里看,如果你有任何指导我应该在哪里解决这个问题会有所帮助。 那不知道是不是你的客户端和服务端的网络连接。 我认为这个答案没有抓住重点。在我们开始提出索引建议之前,让我们回顾一下 SQL 语句,看看是否有迹象表明我们正在尝试调整的内容可能不会返回我们期望的结果,以及是否有迹象表明它可能会强制执行低效操作。 【参考方案2】: 如果您只想获取COUNT(*),请不要获取m.*。 如果phpfox_channel_category 是多对多映射表,请遵循http://mysql.rjweb.org/doc.php/index_cookbook_mysql#many_to_many_mapping_table 中的提示 m 需要INDEX(in_process, view_id, module_id, item_id, privacy),顺序不限。

避免GROUP BY

         INNER JOIN  phpfox_channel_category AS mc ON(mc.category_id = mc.category_id)
         INNER JOIN  phpfox_channel_category_data AS mcd ON(mcd.video_id = m.video_id)
         AND  mcd.category_id = 17
         GROUP BY  m.video_id

-->(类似)

    AND EXISTS(
                 SELECT 1
                     FROM phpfox_channel_category      AS mc
                     JOIN phpfox_channel_category_data AS mcd
                             ON mcd.video_id = mc.video_id
                     WHERE mcd.video_id = 17
                       AND mc.video_id = m.video_id
              )

【讨论】:

是的。如果我们知道video_idm 的主键,请避免生成中间集,然后生成 GROUP BY 来折叠行。我认为另一个大的性能问题是条件 mc.category_id` 等于自身......看起来mcd 旨在用作连接表,解决videocategory 之间的多对多。但我们只是猜测。我完全同意;在我们开始抛出索引建议之前,让我们确保我们正在运行我们想要运行的语句。【参考方案3】:

让我们确保我们正在优化正确的查询。我建议我们在ON 子句中检查这个条件:

 mc.category_id = mc.category_id

我们知道对于mc 中的每一行,category_id 的非 NULL 值都是 TRUE。我们可以将该条件表示为:

 mc.category_id IS NOT NULL

这意味着连接几乎是交叉连接;从m 返回的每一行都与mc 的每一行匹配。也就是说,我们可以得到一个等价的结果:

   FROM phpfox_channel_video    m
   JOIN phpfox_channel_category    mc
     ON mc.category_id IS NOT NULL

我怀疑这实际上不是我们想要的结果。我认为我们的意思是匹配m.category_id。但这只是猜测。


如果video_id 列是m 上的PRIMARY KEY 或UNIQUE KEY,我们可以通过使用具有相关子查询的EXISTS 来避免创建重复行的连接,从而避免可能代价高昂的GROUP BY 操作。如果我们可以避免生成带有重复值video_id 的中间结果,那么我们就可以避免执行GROUP BY 的需要。

另外,对于内联视图查询,我们可以只返回我们需要的表达式,而不是返回所有列*。在外部查询中,唯一引用的列是user_id

所以我们可以这样写:

SELECT COUNT(*) AS total_rows
  FROM (
         SELECT m.user_id
           FROM phpfox_channel_video m
          WHERE EXISTS ( SELECT 1
                           FROM phpfox_channel_category mc
                          WHERE mc.category_id  = m.category_id
                      --        mc.category_id = mc.category_id  -- <original
                       )
            AND EXISTS ( SELECT 1
                           FROM phpfox_channel_category_data mcd
                          WHERE mcd.video_id     = m.video_id
                            AND mcd.category_id  = 17
                       )
            AND m.in_process  = 0
            AND m.view_id     = 0
            AND m.module_id   = 'videochannel'
            AND m.item_id     = 0
            AND m.privacy   IN (0)
          ORDER BY m.time_stamp DESC
          LIMIT 12
       ) d
    JOIN phpfox_user u
      ON u.user_id = d.user_id

为了调整,m 的最佳索引将具有具有相等谓词的前导列,然后是 time_stamp 列,这样我们就可以避免“使用文件排序”操作,ORDER BY 可以通过返回行来满足按索引顺序。看起来我们需要对行进行排序的原因是为了 LIMIT 子句。

... ON phpfox_channel_video (in_process, view_id, item_id, module_id
          , time_stamp, video_id, ... )

另外两个表,我们需要具有相等谓词的前导列的索引

... ON phpfox_channel_category_data (video_id, category_id, ...)

... ON phpfox_channel_category ( category_id, ... )

注意事项:

(不完全清楚为什么我们需要内联视图,并且我们正在延迟 user_id 引用的连接。再说一次,整个查询的重点对我来说并不是很明显;我只是提供重写,给定提供的 SQL,更改为条件category_id。)

以上假设category_id列存在于m中,并且是一对多的关系。

但如果这不是真的...如果mcd 表实际上是连接表,解决视频和类别之间的多对多关系,这样连接条件就应该是

 mcd.category_id = mc.category_id 
   ^

然后我们希望将上面查询中的WHERE EXISTSAND EXISTS 替换为单个相关子查询。像这样的:

SELECT COUNT(*) AS total_rows
  FROM (
         SELECT m.user_id
           FROM phpfox_channel_video m
          WHERE EXISTS ( SELECT 1
                           FROM phpfox_channel_category mc
                           JOIN phpfox_channel_category_data mcd
                             ON mcd.category_id  = mc.category_id 
                          WHERE mcd.video_id     = m.video_id
                            AND mcd.category_id  = 17
                       )
            AND m.in_process  = 0
            AND m.view_id     = 0
            AND m.module_id   = 'videochannel'
            AND m.item_id     = 0
            AND m.privacy   IN (0)
          ORDER BY m.time_stamp DESC
          LIMIT 12
       ) d
    JOIN phpfox_user u
      ON u.user_id = d.user_id

【讨论】:

感谢我的帮助。我尝试运行你建议的查询并得到错误 MySQL 说:#1054 - Unknown column 'mcd.category' in 'on Clause' 看起来列引用可能是 mcd.category_id 。我并不是要为您提供 SQL 解决方案;我的目的是让你更清楚地思考我们真正想要返回的结果。就规范而言,除了提供的 SQL 和匹配的 mc.category_id = mc.category_id 之外,我没有什么可做的。至于哪个其他表实际上包含应该与mc.category_id 匹配的列,我只是在猜测。最后一个查询基于mcd 是一个联结表,mmc 之间的多对多关系。只是猜测。 回显答案的开头......让我们确保我们正在调整语句,确保语句返回我们实际想要返回的结果,并确保我们正在使用为我们提供最佳性能的最佳模式(例如,避免创建巨大的中间结果集并强制使用文件排序操作折叠行。)是的,添加索引可以提高性能,但仅此而已;通常最大的性能提升来自于重写查询。 如果mcd 是一个联结表,那么很可能我们已经定义了外键,对于 InnoDB,我们已经有索引来支持约束。在我们提出有关添加索引的建议之前,我们确实需要知道已经存在哪些索引。例如,如果(video_id,category_id) 元组是唯一的,那么我们可能希望(video_id,category_id)(video_id,category_id) 上的唯一索引,并且我们希望仅删除video_idcategory_id 上的任何单例索引。创建合适的索引是性能调优的重要组成部分,但它只是其中的一部分。 感谢您的宝贵时间。我认为这个性能问题可能是我会遗漏的更基本的问题,但由于我不是数据库专家,所以我无法应用你所有的推理和思考。我必须在数据库相关的东西上达到高级水平,或者至少比我有更好的知识。在这一切结束时。我所做的是微调 my.CNF 文件并查看索引并没有太大帮助。它似乎更多地与结构和发展本身有关。非常感谢。

以上是关于MySQL查询慢不知道如何优化的主要内容,如果未能解决你的问题,请参考以下文章

mysql查询所用时间过长 如何优化?

如何优化mysql查询

如何优化 MySQL 数据库/查询

MySQL查询优化之explain的深入解析

MySQL监控及优化

大型单表的 MySQL 查询优化 [关闭]