我有一个SQL请求,它在工作时占用了我100%的VM CPU。我想知道如何优化它:
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:
centreon.hostgroup_relation:
centreon.acl_resources_hg_relations:
centreon.acl_resources:
答案 0 :(得分:0)
对于SQL Server
,可以使用MAXDOP
例如,您可以在查询结尾处定义
option (maxdop 2)
我非常确定MySql
中的等价物。
如果执行时间不相关,您可以尝试接近这种情况。
答案 1 :(得分:0)
创建一个临时表,其中确认条件,模式将在最终结果中包含所需的列,并在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;
或使用正确的列定义创建
更新左边连接的所有表中的字段,可以在更新中使用内部连接。您应该编写7个不同的更新语句。下面给出了两个例子。
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;
类似地使用Join with Logs从日志更新ctime,这是第8次更新声明。
可以为此写一个sp。
答案 2 :(得分:0)
将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)
答案 3 :(得分:0)
在哪里是时间杀手! 代替 logs.status IN(1,2,3) 使用 logs.status = 1或logs.status = 2或logs.status = 3
答案 4 :(得分:0)
我已经轻松地重新格式化了我的可读性参考查询,并且更好地查看了表之间的关系......否则忽略该部分。
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
我首先建议您使用&#34;致谢&#34;表并且索引至少为(entry_time,acknowledgement_id)。接下来,更新WHERE子句。因为您正在运行一个函数来将unix时间戳转换为日期并分别抓住YEAR(和月份),所以我不相信它正在利用索引,因为它必须为每一行计算。为了提升它,unix时间戳只是一个代表特定时间点的秒的数字。如果您要查找特定月份,则预先计算开始和结束的unix时间并运行该范围。有点像...
和a.entry_time&gt; = UNIX_TIMESTAMP(&#39; 2015-10-01&#39;) 和a.entry_time&lt; UNIX_TIMESTAMP(&#39; 2015-11-01&#39;)
这样,它可以计算10月31日11月1日11:59:59之间的所有秒数。
然后,没有我的眼镜可以更清楚地看到所有图像,而且今天早上的时间很短,我会确保每张桌子上至少有以下索引
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 )
最后,您的相关子查询字段将成为每个行处理的杀手,但希望其他索引优化提示有助于提高性能。