我有几个MySQL表来存储来自传感器的温度数据。传感器每分钟报告一次,并且有数十个传感器(并且正在增长)。这些表已迅速增长到数百万行,并将继续增长。两个相关表格为data
和data_temperature
。
data
表的结构如下:
data_id bigint(20) unsigned NOT NULL AUTO_INCREMENT
created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
sensor_id int(10) unsigned NOT NULL
data_temperature
表的结构如下:
temperature_id bigint(20) unsigned NOT NULL AUTO_INCREMENT
created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
data_id bigint(20) unsigned NOT NULL
x_id varchar(32) DEFAULT NULL
x_sn varchar(16) DEFAULT NULL
x_unit char(1) DEFAULT NULL
x_value` decimal(6,2) DEFAULT NULL
由于每个传感器每分钟报告一次,每个传感器每天应该有大约1440行。但数据中偶尔存在差距,有时会持续数分钟,有时会持续更长时间。
我需要选择要在图表上显示的数据样本。图表宽度为600像素。虽然图表的时间范围是可变的(有时是每日图表,有时是每周图表,有时是每年图表等),但图表的像素宽度是固定的。
最初我会选择时间范围内的行数,然后将其除以600得到X
,然后选择data_id MOD X = 0
的行。但除非只有一个传感器向表格报告,否则这不会很好。有许多传感器,它会产生很多空隙。为了弥补这一点,我提取的数据远远超过需要的数量,并且过多地填充图表以确保没有漏洞。
人满为患会导致浏览器渲染时间变慢。但即使SELECT COUNT()
现在也是服务器端缓慢的主要原因,在data
表上运行大约需要5-6秒。
理想情况下,我想要做的是从表中选择数据,以便在给定窗口中我只有一个数据点(但在没有数据的情况下为零)。窗口是图表中查看的总时间范围除以图形的宽度(以像素为单位)。因此,查看600px宽的每日图表将按如下方式计算:
86400 seconds per day / 600 pixels = 144-second window
所以我希望每144秒不超过一个数据点。这是我到目前为止提出的查询:
SELECT data_temperature.data_id, data_temperature.created,
ROUND( data_temperature.x_value, 1 ) AS temperature
FROM data_temperature
INNER JOIN data
ON data_temperature.data_id = data.data_id
WHERE data.sensor_id = :sensor_id
AND data.created BETWEEN :dt_start AND :dt_end
GROUP BY ROUND( UNIX_TIMESTAMP( data_temperature.created ) / 144 )
ORDER BY data.created, data.data_id
此查询是一种改进,因为它返回正确的数据,但也在大约3.6秒内运行。这仍然比我真正想要的要慢得多。因此,我想知道是否有任何其他想法可以通过更有效的查询来实现这一目标。
注意:尽管它看起来不正确,但即使它们的关系是1比1,也有一个很好的理由让data
和data_temperature
表分开。当我修改我的查询和结构以便所有内容都在一个表中时,它不会改善查询时间。因此,我不认为有两个表对性能产生负面影响。
根据@Kevin Nelson的回复更新澄清
这不是GROUP BY那么慢,BETWEEN
子句中WHERE
的速度很慢。如果我删除它,它运行得更快,但当然返回错误的结果。如果我执行这样的简单查询:
SELECT data.data_id, data.created
FROM data
WHERE data.created BETWEEN :dt_start AND :dt_end
它也很慢。我的created
列已编入索引,因此我不太清楚为什么。我知道dt_start
和dt_end
之间的范围越大,它就越慢。一天的续航时间大约需要半秒钟。一周的续航时间约为10秒。
答案 0 :(得分:0)
如果我的整体问题出错了,我很抱歉,但听起来你在询问如何在选择行时优化表格以获得最佳速度,因为您使用的GROUP BY应该是我能看到的所有工作。如果你的where条件是针对索引列的,那么GROUP BY不应该明显减慢它。
但是,您可以采取一些措施来加速表查询:
1)使用InnoDB表,使主键成为sensor_id和创建PRIMARY KEY (created,sensor_id)
的组合。 InnoDB使用Clustered Index作为主键,因此它不必搜索索引然后查找数据。但是,如果可能,您希望确保按主键的顺序插入行,以便它可以将其放在最后。
2)使用表分区。每月制作一个分区或其他一些时间将创建可以独立搜索的单独文件。您只需确保在WHERE子句中使用分区列,否则它必须搜索每个文件。
http://dev.mysql.com/doc/refman/5.6/en/partitioning.html
[基于评论更新和Q更新]
相信我,我比你想象的更了解你的模型。我的业务几乎相同。对于我目前的工作,我们的恒温器每月有大约7000万条记录并且发展迅速。我们只捕获每5分钟的数据而不是每分钟。我们总共有超过10亿条记录。分区(手动或使用MySQL的内置分区)将月份分解为自己的文件,以便任何给定的搜索只需要经过给定月份的数据而不是整个数据库。所以,我不确定为什么你会认为分区不可扩展。分区的全部意义在于可扩展性。
我唯一想到的另一个想法是每个传感器每月一个NoSQL文件,这可能是速度的终极目标,但我还不太了解NoSQL还没知道所有的ins和奏。
但无论如何对于MySQL,使用我在InnoDB表上提到的70密耳记录,其中主键是(macAddress,timestamp)...以获取2天的条目(576条记录)需要0.140秒。我的本地计算机是一台速度慢得多的计算机,同一查询只需0.187秒。正如我所提到的,因为主键是Clustered Index,它是WITH数据...所以数据实际上是按mac,timestamp排序的。因此,当它找到索引时,它会找到数据。使用标准的MySQL索引,您的代码必须找到将其指向数据的索引,然后它必须单独获取数据,这会增加时间。
如果您使用MySQL工作台,我相信这是持续时间/提取之间的区别。如果您看到持续时间较长,则无法找到数据。如果你看到低持续时间和高获取,那么(我认为,但不完全确定)它很快就能找到数据的索引,但是在搜索找到所有这些指针位置时获取它需要时间。当我搜索聚簇索引时,我的获取时间是0.031秒。
无论您是否按照建议使用聚簇索引,最后都需要对查询执行EXPLAIN SELECT...
并确保它实际使用您期望的索引。如果没有,你需要找出原因。至少,如果你没有,我会创建索引:
INDEX bySensorAndTime (sensor_id, created)
这样,MySQL只需要为您的查询使用一个索引,因为 - 我猜测 - 您总是会使用WHERE
中的这两个字段进行搜索。