【问题标题】:SQL request optimizationSQL 请求优化
【发布时间】:2015-08-02 18:00:54
【问题描述】:

我有一个 SQL 请求,它在工作时占用了我的 VM CPU 的 100%。我想知道如何优化它:

SELECT g.name AS hostgroup
     , h.name AS hostname
     , a.host_id
     , s.display_name AS servicename
     , a.service_id
     , a.entry_time AS ack_time
     , (  SELECT ctime 
          FROM logs 
          WHERE logs.host_id = a.host_id 
          AND logs.service_id = a.service_id 
          AND logs.ctime < a.entry_time 
          AND logs.status IN (1, 2, 3) 
          AND logs.type = 1 
          ORDER BY logs.log_id DESC 
          LIMIT 1) AS start_time
     , ar.acl_res_name AS timeperiod
     , a.state AS state
     , a.author
     , a.acknowledgement_id AS ack_id
FROM centstorage.acknowledgements a
LEFT JOIN centstorage.hosts h ON a.host_id = h.host_id
LEFT JOIN centstorage.services s ON a.service_id = s.service_id
LEFT JOIN centstorage.hosts_hostgroups p ON a.host_id = p.host_id
LEFT JOIN centstorage.hostgroups g ON  g.hostgroup_id = p.hostgroup_id
LEFT JOIN centreon.hostgroup_relation hg ON a.host_id = hg.host_host_id
LEFT JOIN centreon.acl_resources_hg_relations hh ON hg.hostgroup_hg_id = hh.hg_hg_id
LEFT JOIN centreon.acl_resources ar ON hh.acl_res_id = ar.acl_res_id
WHERE ar.acl_res_name != 'All Resources'
AND YEAR(FROM_UNIXTIME( a.entry_time )) = YEAR(CURDATE())
AND MONTH(FROM_UNIXTIME( a.entry_time )) = MONTH(CURDATE())
AND a.service_id is not null
ORDER BY a.acknowledgement_id ASC

问题出在这部分:

(SELECT ctime FROM logs
 WHERE logs.host_id = a.host_id
   AND logs.service_id = a.service_id
   AND logs.ctime < a.entry_time
   AND logs.status IN (1, 2, 3)
   AND logs.type = 1
 ORDER BY logs.log_id DESC
 LIMIT 1) AS start_time

表日志真的很大,一些朋友告诉我使用缓冲表/数据库,但我很了解这些事情,我不知道该怎么做。

查询有一个 EXPLAIN EXTENDED :

看来他只会检查表日志的 2 行,为什么要花这么多时间? (表日志中有 560000 行)。

这是这些表的所有索引:

centstorage.acknowledgements:

centstorage.hosts:

centstorage.services:

centstorage.hosts_hostgroups :

centstorage.hostgroups :

centeron.hostgroup_relation :

centeron.acl_resources_hg_relations:

centeron.acl_resources:

【问题讨论】:

  • 哪个 dbms 产品?表和索引定义等
  • 首先检查执行计划并验证是否缺少索引。
  • 他们都需要LEFT JOINS吗?表是否充分索引
  • 子查询是性能杀手,您应该尝试使用派生表。
  • 附注:为什么不希望查询尽可能多地占用 CPU?与“哦,让我们用 10% 的 CPU 运行这个查询,它会花费 10 倍的时间”相比,这肯定是可取的?

标签: mysql sql optimization


【解决方案1】:

对于SQL Server,可以使用MAXDOP 定义查询的最大并行度

例如,您可以在查询结束时定义

option (maxdop 2) 

我很确定MySql 中有一个等价物。

如果执行时间不相关,您可以尝试处理这种情况。

【讨论】:

  • mysql没有这样的东西,第三方插件也没有
【解决方案2】:
  1. 根据确认条件创建一个临时表,架构将在最终结果中包含所需的列,并在 JOIN 中与所有 7 个表一起使用

    CREATE TEMPORARY TABLE __tempacknowledgements AS SELECT g.name AS hostgroup
         , '' AS hostname
         , a.host_id
         , s.display_name AS servicename
         , a.service_id
         , a.entry_time AS ack_time
         , '' AS AS start_time
         , '' AS timeperiod
         , a.state AS state
         , a.author
         , a.acknowledgement_id AS ack_id
    FROM centstorage.acknowledgements a
    WHERE YEAR(FROM_UNIXTIME( a.entry_time )) = YEAR(CURDATE())
    AND MONTH(FROM_UNIXTIME( a.entry_time )) = MONTH(CURDATE())
    AND a.service_id IS NOT NULL
    ORDER BY a.acknowledgement_id ASC;
    

或使用适当的列定义创建

  1. 更新所有已左连接的表中的字段,您可以在更新中使用内连接。您应该编写 7 个不同的更新语句。下面给出2个例子。

    UPDATE __tempacknowledgements a JOIN centstorage.hosts h USING(host_id)
    SET a.name=h.name;
    
    UPDATE __tempacknowledgements s JOIN centstorage.services h USING(service_id)
    SET a.acl_res_name=s.acl_res_name;
    
  2. 使用 Join with Logs 从日志中更新 ctime 的类似方式,这是第 8 条更新语句。

  3. 从临时表中选择。
  4. 删除临时表

可以为此编写一个 sp。

【讨论】:

  • 似乎是帮助我的好方法,但我仍然是 SQL 复杂查询的初学者......所以我不明白
  • 添加了一些示例查询,可以帮助您了解建议的解决方案。
【解决方案3】:

LEFT JOIN 转换为JOIN,除非您确实需要LEFT

AND YEAR(FROM_UNIXTIME( a.entry_time )) = YEAR(CURDATE())
AND MONTH(FROM_UNIXTIME( a.entry_time )) = MONTH(CURDATE())
AND a.service_id is not null

你有a.service_id is not null 的行吗?如果没有,请摆脱它。

如前所述,日期比较并未优化。以下是改用的内容:

AND a.entry_time >= CONCAT(LEFT(CURDATE(), 7), '-01')
AND a.entry_time <  CONCAT(LEFT(CURDATE(), 7), '-01') + INTERVAL 1 MONTH

并且添加其中之一(取决于我上面的评论):

INDEX(entry_time)
INDEX(service_id, entry_time)

相关子查询很难优化。这个索引(logs)可能会有所帮助:

INDEX(type, host_id, service_id, status)

【讨论】:

    【解决方案4】:

    时间杀手在哪里! 代替 logs.status IN (1, 2, 3) 采用 logs.status=1 或 logs.status=2 或 logs.status=3

    【讨论】:

    • 您能详细说明一下吗?
    【解决方案5】:

    我已经稍微重新格式化了查询以供阅读参考并更好地查看表之间的关系...否则请忽略该部分。

    SELECT 
          g.name AS hostgroup, 
          h.name AS hostname, 
          a.host_id, 
          s.display_name AS servicename, 
          a.service_id, 
          a.entry_time AS ack_time,
          ( SELECT 
                  ctime 
               FROM 
                  logs 
               WHERE 
                      logs.host_id = a.host_id 
                  AND logs.service_id = a.service_id 
                  AND logs.ctime < a.entry_time 
                  AND logs.status IN (1, 2, 3) 
                  AND logs.type = 1 
               ORDER BY 
                  logs.log_id DESC 
               LIMIT 1) AS start_time, 
          ar.acl_res_name AS timeperiod, 
          a.state AS state, 
          a.author, 
          a.acknowledgement_id AS ack_id
       FROM 
          centstorage.acknowledgements a
             LEFT JOIN centstorage.hosts h 
                ON a.host_id = h.host_id
             LEFT JOIN centstorage.services s 
                ON a.service_id = s.service_id
             LEFT JOIN centstorage.hosts_hostgroups p 
                ON a.host_id = p.host_id
                LEFT JOIN centstorage.hostgroups g 
                   ON p.hostgroup_id = g.hostgroup_id 
             LEFT JOIN centreon.hostgroup_relation hg 
                ON a.host_id = hg.host_host_id
                LEFT JOIN centreon.acl_resources_hg_relations hh 
                   ON hg.hostgroup_hg_id = hh.hg_hg_id
                   LEFT JOIN centreon.acl_resources ar 
                      ON hh.acl_res_id = ar.acl_res_id
       WHERE 
              ar.acl_res_name != 'All Resources'
          AND YEAR(FROM_UNIXTIME( a.entry_time )) = YEAR(CURDATE())
          AND MONTH(FROM_UNIXTIME( a.entry_time )) = MONTH(CURDATE())
          AND a.service_id is not null
       ORDER BY 
          a.acknowledgement_id ASC
    

    我首先建议从您的“致谢”表开始,并至少有一个索引( entry_time, acknowledgement_id )。接下来,更新您的 WHERE 子句。因为您正在运行一个函数来将 unix 时间戳转换为日期并分别获取 YEAR(和月份),所以我不相信它正在使用索引,因为它必须为每一行计算索引。为了提升这一点,unix 时间戳只不过是一个表示从特定时间点开始的秒数的数字。如果您正在寻找特定月份,则预先计算开始和结束 unix 时间并在该范围内运行。比如……

    和 a.entry_time >= UNIX_TIMESTAMP('2015-10-01') 和 a.entry_time

    这样,它计算了当月内的所有秒数,直到 10 月 31 日 11:59:59,就在 11 月 1 日之前。

    那么,今天早上不戴眼镜更清楚地看到所有图像,我会确保您在每张桌子上至少分别有以下索引

    table              index
    logs               ( host_id, service_id, type, status, ctime, log_id )
    acknowledgements   ( entry_time, acknowledgement_id, host_id, service_id )
    hosts              ( host_id, name )
    services           ( service_id, display_name )
    hosts_hostgroups   ( host_id, hostgroup_id )
    hostgroups         ( hostgroup_id, name )
    hostgroup_relation ( host_host_id, hostgroup_hg_id )
    acl_resources_hg_relations ( hh_hg_id, acl_res_id )
    acl_resources ar   ( acl_res_id, acl_res_name )
    

    最后,您的相关子查询字段将成为一个杀手,因为它会针对每一行进行处理,但希望其他索引优化想法将有助于提高性能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-10-02
      • 2015-07-11
      • 1970-01-01
      • 2021-09-05
      • 2019-02-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多