使用生成的GROUP BY语句优化SQL查询

时间:2010-11-10 13:11:18

标签: php mysql optimization query-optimization

我有这个问题:

   SELECT ROUND(AVG(temp)*multT + conT,2) as temp,
          FLOOR(timestamp/$secondInterval) as meh
     FROM sensor_locass
LEFT JOIN sensor_data USING(sensor_id)
    WHERE sensor_id = '$id'
      AND project_id = '$project'
 GROUP BY meh
 ORDER BY timestamp ASC

目的是选择绘制图形的数据,我使用一个像素值的数据的平均值来使图表忠实于数据。

到目前为止,优化包括添加索引,在MyISAM和InnoDB之间切换,但没有运气。

由于时间间隔随图形缩放和数据收集周期而变化,因此无法为GROUP BY语句创建单独的列,但查询速度很慢。有没有人有优化此查询或表格的想法,以便更快地进行此分组,我目前在timestampsensor_idproject_id列,timestamp索引上有一个索引然而,没有使用。

使用查询运行explain extended时,我得到以下内容:

1   SIMPLE  sensor_locass   ref     sensor_id_lookup,project_id_lookup  sensor_id_lookup    4   const                               2       100.00  Using where; Using temporary; Using filesort
1   SIMPLE  sensor_data     ref     idsensor_lookup idsensor_lookup                         4   webstech.sensor_locass.sensor_id    66857   100.00

sensor_data表目前包含270万个数据点,这只是我最终必须处理的数据量的一小部分。任何有用的想法,评论或解决方案都是最受欢迎的

编辑表格定义:

CREATE TABLE `sensor_data` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `gateway_id` int(11) NOT NULL,
 `timestamp` int(10) NOT NULL,
 `v1` int(11) NOT NULL,
 `v2` int(11) NOT NULL,
 `v3` int(11) NOT NULL,
 `sensor_id` int(11) NOT NULL,
 `temp` decimal(5,3) NOT NULL,
 `oxygen` decimal(5,3) NOT NULL,
 `batVol` decimal(4,3) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `gateway_id` (`gateway_id`),
 KEY `time_lookup` (`timestamp`),
 KEY `idsensor_lookup` (`sensor_id`)
) ENGINE=MyISAM AUTO_INCREMENT=2741126 DEFAULT CHARSET=latin1

CREATE TABLE `sensor_locass` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `project_id` int(11) NOT NULL,
 `sensor_id` int(11) NOT NULL,
 `start` date NOT NULL,
 `end` date NOT NULL,
 `multT` decimal(6,3) NOT NULL,
 `conT` decimal(6,3) NOT NULL,
 `multO` decimal(6,3) NOT NULL,
 `conO` decimal(6,3) NOT NULL,
 `xpos` decimal(4,2) NOT NULL,
 `ypos` decimal(4,2) NOT NULL,
 `lat` decimal(9,6) NOT NULL,
 `lon` decimal(9,6) NOT NULL,
 `isRef` tinyint(1) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `sensor_id_lookup` (`sensor_id`),
 KEY `project_id_lookup` (`project_id`)
) ENGINE=MyISAM AUTO_INCREMENT=238 DEFAULT CHARSET=latin1

5 个答案:

答案 0 :(得分:2)

尽管每个人都有答案,但更改主键以优化238行表格上的搜索并不会改变任何内容,尤其是当EXPLAIN显示单个键将搜索范围缩小到两行时。将timestamp添加到sensor_data上的主键将无法正常工作,因为没有任何内容正在查询时间戳,只计算它(除非你可以像galymzhan建议的那样限制时间戳值)。

哦,你可以在你的查询中删除LEFT,因为project_id上的匹配无论如何都会使它无关紧要(但不会减慢任何速度)。如果这些变量来自客户输入以避免$project_id = "'; DROP TABLES; --"类型sql注入攻击,请不要将变量直接插入到查询中。

调整堆大小可能会有一段时间,但如果需要扩展,则必须继续调整它。

答案vdrmrt建议可能有效,但是你需要用$ secondInterval的每一个可能值填充你的聚合表,考虑到你说你需要的灵活性,我假设这个值不是很合理。同样,您可以考虑rrdtool,直接使用它或以与它相同的方式修改数据。我具体指的是它将原始数据保留给定的一段时间(通常是几天),然后在越来越大的时间段内将数据点平均在一起。最终结果是您可以放大最近一段时间的高细节,但如果再往后看,数据已经被有效地有损压缩到大部分时间内的平均值(例如,每天一个数据点,一天,一个星期每分钟一个数据点,一个月每小时一个数据点,等等。您可以最初自定义这些平均值,但除非您同时保留原始数据和汇总数据,否则您将无法返回并进行调整。特别是,您无法动态放大某些较旧的任意点的高细节(例如查看六个月前发生的1小时的每秒数据)。

因此,根据您的要求,您必须决定这些限制是否合理。

如果没有,我会争辩说你正试图在MySQL中做一些不是为它设计的东西。我建议拉出你需要的原始数据,并在php中取平均值,而不是在你的查询中。正如已经指出的那样,查询需要很长时间的主要原因是因为GROUP BY子句强制mysql处理内存中的所有数据,但由于它的数据太多,它实际上将该数据临时写入磁盘。 (因此using filesort)。但是,就可以在php中使用多少内存而言,你有更多的灵活性。此外,由于您要组合附近的行,您可以逐行拉出数据,将其组合在一起,从而永远不需要在PHP进程中将所有行保留在内存中。然后,您可以删除GROUP BY并避免使用filesort。请改用ORDER BY timestamp,如果mysql没有正确优化,请确保使用FORCE INDEX FOR ORDER BY (timestamp)

答案 1 :(得分:1)

我建议您找到表的自然主键并切换到InnoDB。这可以猜测您的数据是什么样的:

sensor_data: PRIMARY KEY (sensor_id, timestamp)

sensor_locass: PRIMARY KEY (sensor_id, project_id)

InnoDB将以这种方式订购所有数据,因此您可能会SELECT在一起的行将在磁盘上。我认为你是小伙伴总会造成一些麻烦。如果您可以将其保持在切换到文件排序(tmp_table_sizemax_heap_table_size)的大小以下,那么速度会快得多。

您通常会返回多少行?现在需要多长时间?

答案 2 :(得分:0)

正如Joshua建议的那样,你应该将(sensor_id,project_id)定义为sensor_locass表的主键,因为目前表中每列有2个独立的索引。根据mysql文档,SELECT将仅从它们中选择一个索引(最具限制性,找到较少的行),而主键允许使用两个列来索引数据。

然而,EXPLAIN显示MySQL在连接表上检查了66857行,因此您也应该以某种方式对其进行优化。也许您可以在给定的时间间隔内查询传感器数据,例如timestamp BETWEEN (begin, end)

答案 3 :(得分:0)

我同意第一步应该是将sensor_id,project_id定义为sensor_locass的主键。 如果这还不够,并且您的数据是相对静态的,您可以创建一个聚合表,您可以每天刷新,而不是从那里查询。 您还需要做的是为secondInterval定义一个范围,将其存储在新表中,并将该字段添加到聚合表的主键中。

填充聚合表的查询将是这样的:

INSERT INTO aggregated_sensor_data (sensor_id,project_id,secondInterval,timestamp,temp,meh)
SELECT
    sensor_locass.sensor_id,
    sensor_locass.project_id,
    secondInterval,
    timestamp,
    ROUND(AVG(temp)*multT + conT,2) as temp,
    FLOOR(timestamp/secondInterval) as meh
FROM
    sensor_locass
    LEFT JOIN sensor_data
    USING(sensor_id)
    LEFT JOIN secondIntervalRange
    ON 1 = 1
WHERE
        sensor_id = '$id'
    AND
        project_id = '$project'
GROUP BY
    sensor_locass.sensor_id,
    sensor_locass.project_id,
    meh
ORDER BY
    timestamp ASC

您可以使用此查询提取聚合数据:

SELECT
    temp,
    meh
FROM
    aggregated_sensor_data
WHERE
     sensor_id = '$id'
    AND project_id = '$project'
    AND secondInterval = $secondInterval
ORDER BY
    timestamp ASC   

答案 4 :(得分:0)

如果您想使用时间戳索引,则必须明确告知使用该索引。 MySQL 5.1 支持USE INDEX FOR ORDER BY/FORCE INDEX FOR ORDER BY。在这里查看http://dev.mysql.com/doc/refman/5.1/en/index-hints.html