多个JOIN的MySQL性能问题

时间:2014-08-31 19:18:09

标签: mysql sql performance join

我正在开发一种能够监控电能消耗或能源生产的专业网络服务(即来自光伏或风能源)。该项目使用专有电子(由我开发)收集电压,电流和相角等电气参数。

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

Apache服务器没有显示任何性能问题,尽管MySQL服务器具有很强的计算能力,但我无法在不到60秒的时间内执行查询。我已经使用了所有工具(键和索引)并正确设计了查询(我希望),但我无法理解错误。

我在数据库设计方面的经验主要来自Oracle和SQL Server,我对MySQL的经验非常有限(专业)。

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

数据库非常简单:

表1:DATA_RAW由托管电气参数的多个字段以及包含数据raw的TIMESTAMP的SRV_TIMESTAMP字段组成,DEVICE_ID字段包含远程设备ID。

所有远程设备每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行。

将字段顺序更改为连接并不会带来任何好处。 错误在哪里?

1 个答案:

答案 0 :(得分:1)

我做了一些测试,并在我的硬件上查询了不到0.2秒(Intel Xeon CPU E3-1220(4核),16GB,运行Linux和MariaDb(直接替换mysql))

首先,我创建了如下表格。请注意,我增加了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,导致data_raw和data_sun各有46080行数据,data_weather中有384行。

使用SELECT * FROM data_raw查询 data_raw 大约需要0.10秒。

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

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

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

SELECT
  D.SRV_TIMESTAMP, D.DEVICE_ID, D.VOLTAGE, D.CURRENT, D.PHASE,
  S.SUNPOWER, S.SUNAZIMUTH
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);

为了能够进行完整查询,我还在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;

现在我在0.13秒内得到46080行。由于只需要0.10秒来读取data_raw表,我认为它非常好。