优化 SQL 子查询进行统计

Posted

技术标签:

【中文标题】优化 SQL 子查询进行统计【英文标题】:Optimize SQL subquery for statistics 【发布时间】:2015-09-04 09:35:26 【问题描述】:

我为我们的用户 PC 创建了一个简单的统计工具。它每 5 分钟记录一次我们所有 PC 的状态。一个小前端给了我一个使用图表:

现在随着数据的增长,SQL 查询变得越来越慢,我正在寻找优化它的方法。

这是结构。如您所见,“usage”表包含大约 600 万条记录,它使用 mysql InnoDB:

CREATE TABLE IF NOT EXISTS `usage` (
`id` int(11) unsigned NOT NULL,
  `host_id` int(10) unsigned NOT NULL,
  `time` int(10) unsigned NOT NULL,
  `state` enum('LinuxTU','LinuxExt','View','Browser','Idle','Offline') CHARACTER SET latin1 NOT NULL DEFAULT 'Offline'
) ENGINE=InnoDB AUTO_INCREMENT=5963366 DEFAULT CHARSET=utf8;

ALTER TABLE `usage`
 ADD PRIMARY KEY (`id`), ADD KEY `host_id` (`host_id`), ADD KEY `time` (`time`);

ALTER TABLE `usage`
MODIFY `id` int(11) unsigned NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=5963366;

执行以下查询大约需要 7 秒。将数据提供给屏幕截图的是查询。

/* create pivot table */
SELECT `time`,
    SUM(IF(state='LinuxTU', statecount, 0)) AS LinuxTU,
    SUM(IF(state='LinuxExt', statecount, 0)) AS LinuxExt,
    SUM(IF(state='View', statecount, 0)) AS View,
    SUM(IF(state='Browser', statecount, 0)) AS Browser
FROM (
    /* get data from last 24h grouped by state */
    SELECT `time`, `state`, COUNT(`state`) statecount
    FROM `usage` u
    /* group by time to get every 5 minutes
       group by state to get the state counter */
    GROUP BY `time`, `state`
    HAVING `time` > 1441271078 AND `time` < 1441357478
) AS s
GROUP BY `time`
ORDER BY `time` ASC

我不知道如何优化它。有什么我错过的吗?还是我需要重新组织结构?有什么提示吗?

【问题讨论】:

【参考方案1】:

除了将time 比较移动到where 子句中,您还可以完全摆脱子查询:

/* create pivot table */
SELECT `time`,
       SUM(state = 'LinuxTU') AS LinuxTU,
       SUM(state = 'LinuxExt') AS LinuxExt,
       SUM(state = 'View') AS View,
       SUM(state = 'Browser') AS Browser
FROM usage u
WHERE `time` > 1441271078 AND `time` < 1441357478
GROUP BY `time`
ORDER BY `time` ASC;

【讨论】:

【参考方案2】:

我认为你的问题在最后

GROUP BY `time`
ORDER BY `time` ASC

由于子查询,您的索引消失了。所以,你应该想办法消除它。

您还可以选择用编程语言进行一些处理吗?只做内选+外选不加总和的变量,加上顺序,再用编程语言进行处理。

或者你必须在查询中写这个?

【讨论】:

我可以通过将表从一个 ENUM 字段更改为多个状态字段(如 LinuxTU=0、LinuxExt=1)来消除数据透视查询。但我认为多个字段使其不灵活并且可能不一致(例如:LinuxTU=1 和 LinuxExt=1)。实际上,前端按原样使用数据。因此,每个查询都会输出所需的数据。【参考方案3】:

我找到了瓶颈。问题是内部查询。 HAVING 似乎比 WHERE 慢得多。所以我尝试了一些不同的查询,现在我得到了这个结果:

需要 7 秒:

SELECT `time`, `state`, COUNT(`state`) statecount
FROM `usage` u
GROUP BY `time`, `state`
HAVING `time` > 1441271078 AND `time` < 1441357478

耗时 0.1 秒:

SELECT `time`, `state`, COUNT(`state`) `statecount`
FROM `usage` u
WHERE `time` > 1441271078 AND `time` < 1441357478
GROUP BY `time`, `state`

并且给了我同样的结果。前端现在更快了。

【讨论】:

以上是关于优化 SQL 子查询进行统计的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 子查询优化思路

mysql的子查询中有统计语句 我该如何优化

MySQL5.7性能优化系列——SQL语句优化——使用物化策略优化子查询

SQL Fundamentals: 子查询 || 分析函数

子查询的 SQL 查询优化

子查询(嵌套子查询)