2014-08-31 128 views
-3

我正在开发专业的基于Web的服务,能够监控电能消耗或能源生产(即来自光伏或风能源)。该项目使用专有电子(由我开发)收集电气参数,如电压,电流和相角。多个JOIN的MySQL性能问题

  • 远程设备将这些参数发送到Apache Web服务器脚本,该脚本将原始数据推送到托管在单独服务器上的MySQL数据库中。
  • 每个远程设备都有自己的DEVICE_ID。
  • 数据每30秒发送一次,因此有一天我们每个设备都有2880行。

虽然MySQL服务器的计算能力很强大,但Apache服务器不显示任何性能问题我无法在不到60秒的时间内执行查询。我使用了所有的工具(键和索引)并正确设计了查询(我希望),但我无法理解错误。我在DB设计方面的经验主要来自Oracle和SQL Server,我在MySQL上的经验非常有限(作为专业人员)。

服务器硬件:在Windows Server 2008上运行的2x Xeon CPU 64位+ 4GB RAM,是的MySQL安装在Windows2008上,因为它是我非常熟悉的平台。

该数据库是非常简单的:

表1:DATA_RAW由托管的电气参数几个字段,加上包含数据生,包含远程设备ID字段DEVICE_ID的时间戳的字段SRV_TIMESTAMP。

所有远程设备每30秒将数据推送到此表中。主键是一个集群:DEVICE_ID + SRV_TIMESTAMP与这些字段一样,不可能有来自同一设备的重复行。

该系统还接收气象数据,如温度,压力,湿度,云等。他们每小时发送一次。这些数据被推送到另一个名为WEATHER_DATA的表中,主键又是一个集群:DEVICE_ID + SRV_TIMESTAMP。唯一的区别是,我们每天在这里每个设备只有24行。

,其中包含有关于太阳辐射对每个设备信息命名SUN_DATA第三个表。这用于计算PV场效率。托管这些数据的表名为SUN_DATA,并包含各种字段,主密钥又是一个集群:DEVICE_ID + SRV_TIMESTAMP。

重要的是要注意的是,SRV_TIMESTAMP在所有设备之间同步,以便任何数据集将共享相同的时隙(每天提供2880个时隙中的一个)是很重要的。

这里从DATA_RAW表来的数据的一个示例:

SRV_TIMESTAMP  | DEVICE_ID | VOLTAGE | CURRENT | PHASE 
----------------------------------------------------------- 
2014-08-21 22:23:30 | 0AF500100 |  243 |  5.4 | 0.01 
2014-08-21 22:23:30 | 0AF456102 |  240 |  3.4 | 0.15 
2014-08-21 22:23:30 | 0BFDE0010 |  239 |  2.4 | 0.65 
2014-08-21 22:23:00 | 0AF500100 |  241 |  5.2 | 0.37 
2014-08-21 22:23:00 | 0AF456102 |  239 |  3.4 | 0.12 
2014-08-21 22:23:00 | 0BFDE0010 |  238 |  2.5 | 0.64 
2014-08-21 22:22:30 | 0AF500100 |  240 |  5.4 | 0.02 
2014-08-21 22:22:30 | 0AF456102 |  236 |  3.2 | 0.16 
2014-08-21 22:22:30 | 0BFDE0010 |  239 |  2.0 | 0.67 

这里从DATA_SUN表来的数据的一个示例:

SRV_TIMESTAMP  | DEVICE_ID | SUNPOWER| SUNAZIMUTH 
------------------------------------------------------ 
2014-08-21 22:23:30 | 0AF500100 | 845674 |  175.1 
2014-08-21 22:23:30 | 0AF456102 | 866467 |  175.2 
2014-08-21 22:23:30 | 0BFDE0010 | 867686 |  175.4 
2014-08-21 22:23:00 | 0AF500100 | 867685 |  175.6 
2014-08-21 22:23:00 | 0AF456102 | 867876 |  175.9 
2014-08-21 22:23:00 | 0BFDE0010 | 867855 |  176.0 
2014-08-21 22:22:30 | 0AF500100 | 867879 |  176.2 
2014-08-21 22:22:30 | 0AF456102 | 856578 |  176.4 
2014-08-21 22:22:30 | 0BFDE0010 | 876789 |  176.4 

这里来自的数据的样本DATA_WEATHER表:

SRV_TIMESTAMP  | DEVICE_ID | CLOUDS | TEMPERATURE 
------------------------------------------------------ 
2014-08-21 22:00:00 | 0AF500100 |  30 |  36.1 
2014-08-21 22:00:00 | 0AF456102 |  35 |  26.2 
2014-08-21 22:00:00 | 0BFDE0010 |  34 |  35.4 
2014-08-21 21:00:00 | 0AF500100 |  70 |  36.6 
2014-08-21 21:00:00 | 0AF456102 |  10 |  26.9 
2014-08-21 21:00:00 | 0BFDE0010 |  20 |  35.0 
2014-08-21 20:00:00 | 0AF500100 |  30 |  32.2 
2014-08-21 20:00:00 | 0AF456102 |  20 |  23.4 
2014-08-21 20:00:00 | 0BFDE0010 |  65 |  34.4 

请注意,对于仅天气,数据会在每个侯而对于其他表格数据每30秒推动一次。 这里的DATA_RAW表(其他2台都差不多,场只是名称不同)详细的表结构:

CREATE TABLE IF NOT EXISTS `data_raw` (
    `SRV_TIMESTAMP` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 
    `DEVICE_ID` char(5) NOT NULL, 
    `VOLTAGE` decimal(2,0) NOT NULL, 
    `CURRENT` decimal(2,0) NOT NULL, 
    `PHASE` decimal(3,0) NOT NULL 
) 
ENGINE=InnoDB 
DEFAULT CHARSET=utf8 
COMMENT='RAW DATA COMING FROM DEVICE IN A SINGLE TIMESLOT'; 

ALTER TABLE `data_raw` 
    ADD PRIMARY KEY (`DEVICE_ID`,`SRV_TIMESTAMP`) COMMENT 'PRIMARY KEY', 
    ADD KEY `IDX_DEVICE_ID` (`DEVICE_ID`); 

现在的问题:

我需要计算的各种数据,并以这样做,我加入提供气象数据,并与太阳数据电气数据如下:

SELECT 
    D.VOLTAGE, 
    D.CURRENT, 
    S.SUNPOWER1, 
    S.SUNAZIMUTH, 
    W.CLOUDS, 
    W.TEMPERATURE 
FROM 
    DATA_RAW AS D 
    JOIN SUN_DATA AS S ON 
    S.SRV_TIMESTAMP=D.SRV_TIMESTAMP 
    AND S.DEVICE_ID=D.DEVICE_ID 
    LEFT JOIN WEATHER_DATA AS W ON 
    HOUR(W.SRV_TIMESTAMP)=HOUR(D.SRV_TIMESTAMP) 
    AND MONTH(W.SRV_TIMESTAMP)=MONTH(D.SRV_TIMESTAMP) 
    AND YEAR(W.SRV_TIMESTAMP)=YEAR(D.SRV_TIMESTAMP) 
    AND S.DEVICE_ID=D.DEVICE_ID 
ORDER BY D.SRV_TIMESTAMP DESC 

此查询时间超过60秒,在DATA_RAW和SUN_DATA只是40.000行和WEATHER_DATA 150行。

将字段顺序更改为联接没有任何好处。 错误在哪里?

+0

如果不知道表格和索引,很难确切知道出了什么问题,因此您可能希望使问题更加简洁并提供相关信息,但一个显而易见的问题是您在过滤子句中使用函数。即使你有索引,当你使用'HOUR(W.SRV_TIMESTAMP)'之类的东西时,你也可以防止任何索引被使用,并且你将拥有一个全表扫描功能,并将该功能应用于每一行。 – 2014-08-31 19:29:35

+2

1.你的问题很难阅读。请将其分成多个段落。 2.您已经描述了这些表格,但尚未指定您拥有的索引。 3.如果可能的话,提供一个带有真实数据的数据库转储(不需要是实际的数据,但类似的东西) – some 2014-08-31 19:30:16

+0

我已经遵循@some建议,希望现在的问题更容易阅读。 – 2014-08-31 21:56:09

回答

1

我做了一些测试,并在我的硬件(英特尔至强CPU E3-1220(4核),16GB,运行Linux和MariaDb(mysql的替代品)上的查询下降到不到0.2秒)

首先,我创建了如下表格。请注意,我增加了device_id中的字符数,并更改了十进制类型的精度和比例以匹配您提供的示例数据。我还为data_raw添加了一个字段weatherts,该字段包含设备的最新天气报告的时间戳。 (您可以在插入原始数据之前查询最新的天气预报时间戳,并且您还可以在获取天气预报时更新先前记录的时间戳)。

CREATE TABLE IF NOT EXISTS `data_raw` (
    `SRV_TIMESTAMP` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 
    `DEVICE_ID` char(8) NOT NULL, 
    `VOLTAGE` decimal(3,0) NOT NULL, 
    `CURRENT` decimal(2,1) NOT NULL, 
    `PHASE` decimal(3,2) NOT NULL, 
    `weatherts` timestamp 
) 
ENGINE=InnoDB 
DEFAULT CHARSET=utf8 
COMMENT='RAW DATA COMING FROM DEVICE IN A SINGLE TIMESLOT'; 

CREATE TABLE IF NOT EXISTS `data_sun` (
    `SRV_TIMESTAMP` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 
    `DEVICE_ID` char(8) NOT NULL, 
    `SUNPOWER` decimal(10,0) NOT NULL, 
    `SUNAZIMUTH` decimal(4,1) NOT NULL 
) 
ENGINE=InnoDB 
DEFAULT CHARSET=utf8 
COMMENT='SUN DATA'; 


CREATE TABLE IF NOT EXISTS `data_weather` (
    `SRV_TIMESTAMP` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 
    `DEVICE_ID` char(8) NOT NULL, 
    `CLOUDS` decimal(2,0) NOT NULL, 
    `TEMPERATURE` decimal(3,1) NOT NULL 
) 
ENGINE=InnoDB 
DEFAULT CHARSET=utf8 
COMMENT='WEATHER DATA'; 

我创建的创建伪造的数据为16个单位的脚本,每30秒data_raw和data_sun,并每隔一小时data_weather,导致46080行为data_raw和data_sun和在data_weather 384行,每行的数据。

查询data_rawSELECT * FROM data_raw大约需要0.10秒。

我尝试了查询的第一部分,在那里我加入了data_raw和data_sun。如果没有索引需要永远的,所以我对data_sun创建索引:

CREATE UNIQUE INDEX SUN_PKEY ON data_sun (SRV_TIMESTAMP, DEVICE_ID); 

现在下面的查询需要大约0.10秒了。

​​

为了能够做到完整的查询我也data_weather添加索引:

CREATE UNIQUE INDEX WEATHER_PKEY ON data_weather (SRV_TIMESTAMP, DEVICE_ID); 

现在是时候来测试查询:

SELECT 
    D.SRV_TIMESTAMP, D.DEVICE_ID, D.VOLTAGE, D.CURRENT, D.PHASE, 
    S.SUNPOWER, S.SUNAZIMUTH, 
    W.CLOUDS, W.TEMPERATURE 
FROM data_raw AS D 
    LEFT JOIN data_sun AS S ON 
    (S.SRV_TIMESTAMP=D.SRV_TIMESTAMP AND S.DEVICE_ID=D.DEVICE_ID) 
    LEFT JOIN data_weather AS W ON 
    (D.WEATHERTS = W.SRV_TIMESTAMP AND W.DEVICE_ID=D.DEVICE_ID) 
ORDER BY D.SRV_TIMESTAMP, D.DEVICE_ID; 

现在,我得到46080行0.13秒。由于读取data_raw表花费了大约0.10秒,所以我认为它非常好。

+0

太棒了,谢谢! – 2014-09-01 15:35:59

+0

太棒了,谢谢@some! 你给了我几个很好的提示:放置FK链接天气数据,避免在索引上使用函数是最好的想法,我不知道为什么我没有这样。只是因为速度提高了很多。我正在改变表格和相关查询的结构并测试表演。它看起来像问题的根源是HOUR()函数错位在索引上,这导致MySQL不使用索引并扫描整个表。 反正一流的支持!谢谢! – 2014-09-01 15:43:03

+0

@PowerEngineering不错,它为你工作:)请接受这个答案。 – some 2014-09-01 17:35:12