使用MAX()进行MySQL查询优化

时间:2011-11-20 20:55:11

标签: mysql

我有3个表格,其中包含以下架构:

CREATE TABLE  `devices` (
  `device_id` int(11) NOT NULL auto_increment,
  `name` varchar(20) default NULL,
  `appliance_id` int(11) default '0',
  `sensor_type` int(11) default '0',
  `display_name` VARCHAR(100),
  PRIMARY KEY  USING BTREE (`device_id`)
) 

CREATE TABLE  `channels` (
  `channel_id` int(11) NOT NULL AUTO_INCREMENT,
  `device_id` int(11) NOT NULL,
  `channel` varchar(10) NOT NULL,
  PRIMARY KEY (`channel_id`),
  KEY `device_id_idx` (`device_id`)
) 

CREATE TABLE  `historical_data` (
  `date_time` datetime NOT NULL,
  `channel_id` int(11) NOT NULL,
  `data` float DEFAULT NULL,
  `unit` varchar(10) DEFAULT NULL,
  KEY `devices_datetime_idx` (`date_time`) USING BTREE,
  KEY `channel_id_idx` (`channel_id`)
)

设置是设备可以有一个或多个通道,每个通道都有许多(历史)数据。

我使用以下查询来获取一个设备及其所有相关频道的最新历史数据:

SELECT c.channel_id, c.channel, max(h.date_time), h.data 
FROM devices d 
INNER JOIN channels c ON c.device_id = d.device_id 
INNER JOIN historical_data h ON h.channel_id = c.channel_id 
WHERE d.name = 'livingroom' AND d.appliance_id = '0'
AND d.sensor_type = 1 AND ( c.channel = 'ch1') 
GROUP BY c.channel
ORDER BY h.date_time, channel

查询计划如下所示:

+----+-------------+-------+--------+-----------------------+----------------+---------+---------------------------+--------+-------------+
| id | select_type | table | type   | possible_keys         | key            | key_len | ref                       | rows   | Extra       |
+----+-------------+-------+--------+-----------------------+----------------+---------+---------------------------+--------+-------------+
|  1 | SIMPLE      | c     | ALL    | PRIMARY,device_id_idx | NULL           | NULL    | NULL                      |     34 | Using where |
|  1 | SIMPLE      | d     | eq_ref | PRIMARY               | PRIMARY        | 4       | c.device_id               |      1 | Using where |
|  1 | SIMPLE      | h     | ref    | channel_id_idx        | channel_id_idx | 4       | c.channel_id              | 322019 |             |
+----+-------------+-------+--------+-----------------------+----------------+---------+---------------------------+--------+-------------+
3 rows in set (0.00 sec)

上述查询目前大约需要15秒,我想知道是否有任何提示或方法可以改进查询?

编辑: 来自historical_data的示例数据

+---------------------+------------+------+------+
| date_time           | channel_id | data | unit |
+---------------------+------------+------+------+
| 2011-11-20 21:30:57 |         34 | 23.5 | C    |
| 2011-11-20 21:30:57 |          9 |   68 | W    |
| 2011-11-20 21:30:54 |         34 | 23.5 | C    |
| 2011-11-20 21:30:54 |          5 |  316 | W    |
| 2011-11-20 21:30:53 |         34 | 23.5 | C    |
| 2011-11-20 21:30:53 |          2 |   34 | W    |
| 2011-11-20 21:30:51 |         34 | 23.4 | C    |
| 2011-11-20 21:30:51 |          9 |   68 | W    |
| 2011-11-20 21:30:49 |         34 | 23.4 | C    |
| 2011-11-20 21:30:49 |          4 |  193 | W    |
+---------------------+------------+------+------+
10 rows in set (0.00 sec)

编辑2: 多个通道SELECT示例:

SELECT c.channel_id, c.channel, max(h.date_time), h.data 
FROM devices d 
INNER JOIN channels c ON c.device_id = d.device_id 
INNER JOIN historical_data h ON h.channel_id = c.channel_id 
WHERE d.name = 'livingroom' AND d.appliance_id = '0'
AND d.sensor_type = 1 AND ( c.channel = 'ch1' OR c.channel = 'ch2' OR c.channel = 'ch2') 
GROUP BY c.channel
ORDER BY h.date_time, channel

我在c.channel where子句中使用了OR,因为它更容易以编程方式生成,但如果需要可以更改为使用IN。

编辑3: 我想要实现的结果的示例结果:

+-----------+------------+---------+---------------------+-------+
| device_id | channel_id | channel | max(h.date_time)    | data  |
+-----------+------------+---------+---------------------+-------+
|        28 |          9 | ch1     | 2011-11-21 20:39:36 |     0 |
|        28 |         35 | ch2     | 2011-11-21 20:30:55 | 32767 |
+-----------+------------+---------+---------------------+-------+

我已将device_id添加到示例中,但我的选择只需返回channel_id,channel,last date_time,即max和数据。结果应该是一个设备的每个通道的historical_data表中的最后一条记录。

3 个答案:

答案 0 :(得分:1)

似乎删除了在date_time上重新创建索引,通过删除并再次创建它,加快了我的原始SQL,大约2秒

答案 1 :(得分:0)

我无法对此进行测试,所以我想请你运行它,让我们知道会发生什么......如果它能给你想要的结果,并且它的运行速度比你当前的速度快:

CREATE DEFINER=`root`@`localhost` PROCEDURE `GetLatestHistoricalData_EXAMPLE`
  (
      IN param_device_name VARCHAR(20)
    , IN param_appliance_id INT
    , IN param_sensor_type INT
    , IN param_channel VARCHAR(10)
  )
BEGIN

    SELECT 
        h.date_time, h.data 
    FROM 
        historical_data h
        INNER JOIN
        (
            SELECT c.channel_id
            FROM devices d 
            INNER JOIN channels c ON c.device_id = d.device_id 
            WHERE 
                d.name = param_device_name
            AND d.appliance_id = param_appliance_id
            AND d.sensor_type = param_sensor_type
            AND c.channel = param_channel
        ) 
        c ON h.channel_id = c.channel_id 
    ORDER BY h.date_time DESC
    LIMIT 1;

END

然后进行测试:

CALL GetLatestHistoricalData_EXAMPLE ('livingroom', 0, 1, 'ch1');

我尝试将其用于存储过程,以便即使您使用此设备获得所需的结果,也可以使用其他设备尝试并查看结果...谢谢!

[edit] ::响应Danny的评论,这是一个更新的测试版本:

CREATE DEFINER=`root`@`localhost` PROCEDURE `GetLatestHistoricalData_EXAMPLE_3Channel`
  (
      IN param_device_name VARCHAR(20)
    , IN param_appliance_id INT
    , IN param_sensor_type INT
    , IN param_channel_1 VARCHAR(10)
    , IN param_channel_2 VARCHAR(10)
    , IN param_channel_3 VARCHAR(10)
  )
BEGIN

    SELECT 
        h.date_time, h.data 
    FROM 
        historical_data h
        INNER JOIN
        (
            SELECT c.channel_id
            FROM devices d 
            INNER JOIN channels c ON c.device_id = d.device_id 
            WHERE 
                d.name = param_device_name
            AND d.appliance_id = param_appliance_id
            AND d.sensor_type = param_sensor_type
            AND (
                c.channel IN (param_channel_1
                             ,param_channel_2
                             ,param_channel_3
                ) 
        c ON h.channel_id = c.channel_id 
    ORDER BY h.date_time DESC
    LIMIT 1;

END

然后进行测试:

CALL GetLatestHistoricalData_EXAMPLE_3Channel ('livingroom', 0, 1, 'ch1', 'ch2' , 'ch3');

同样,这仅用于测试,因此您将能够看到它是否满足您的需求..

答案 2 :(得分:0)

我首先在设备表(appliance_id,sensor_type,name)上添加一个索引以匹配您的查询。我不知道这个表中有多少条目,但是如果大,每个设备有很多元素,那么就可以了。

其次,在您的频道表上,索引(device_id,频道)

第三,关于你的历史数据,索引(channel_id,date_time)

然后,

SELECT STRAIGHT_JOIN
      PreQuery.MostRecent,
      PreQuery.Channel_ID,
      PreQuery.Channel,
      H2.Data,
      H2.Unit
   from 
      ( select 
              c.channel_id,
              c.channel, 
              max( h.date_time ) as MostRecent
           from 
              devices d

                 join channels c
                    on d.device_id = c.device_id
                    and c.channel in ( 'ch1', 'ch2', 'ch3' )

                    join historical_data h
                      on c.channel_id = c.Channel_id
           where
                  d.appliance_id = 0
              and d.sensor_type = 1
              and d.name = 'livingroom'

           group by
              c.channel_id ) PreQuery 

      JOIN Historical_Data H2
         on PreQuery.Channel_ID = H2.Channel_ID
        AND PreQuery.MostRecent = H2.Date_Time
   order by 
      PreQuery.MostRecent,
      PreQuery.Channel