优化MySQL选择查询

时间:2020-03-05 13:04:47

标签: mysql

MySQL版本::5.7.23 引擎: InnoDB

我创建了一个应用程序,该应用程序使用ICMP回显请求包监视来自世界各地的网络设备。它会定期对设备执行ping操作,并将结果存储在MySQL表中。 我有一个查询,该查询可获取给定设备的最新100个向上/向下事件,但是执行该过程大约需要38秒,这太长了。我正在尝试优化查询,但有点迷失了。

查询:

select
    c.id as clusterId,
    c.name as cluster,
    m.id as machineId,
    m.label as machine,
    h.id as pingResultId,
    h.timePinged as `timestamp`,
    h.status
from pinger_history h
join pinger_history_updown ud on ud.pingResultId = h.id
join pinger_machine_ip_addresses i on h.machineIpId = i.id
join pinger_machines m on i.machineId = m.id
join pinger_clusters c on m.clusterId = c.id
where h.deviceId = ?
order by h.id desc
limit 100

说明查询输出:

+----+-------------+-------+------------+--------+------------------------------+---------+---------+---------------------------+--------+----------+----------------------------------------------+
| id | select_type | table | partitions | type   | possible_keys                | key     | key_len | ref                       | rows   | filtered | Extra                                        |
+----+-------------+-------+------------+--------+------------------------------+---------+---------+---------------------------+--------+----------+----------------------------------------------+
|  1 | SIMPLE      | ud    | NULL       | index  | PRIMARY                      | PRIMARY | 4       | NULL                      | 111239 |   100.00 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | h     | NULL       | eq_ref | PRIMARY,deviceId,machineIpId | PRIMARY | 4       | dashboard.ud.pingResultId |      1 |     5.00 | Using where                                  |
|  1 | SIMPLE      | i     | NULL       | eq_ref | PRIMARY,machineId            | PRIMARY | 4       | dashboard.h.machineIpId   |      1 |   100.00 | NULL                                         |
|  1 | SIMPLE      | m     | NULL       | eq_ref | PRIMARY,clusterId            | PRIMARY | 4       | dashboard.i.machineId     |      1 |   100.00 | Using where                                  |
|  1 | SIMPLE      | c     | NULL       | eq_ref | PRIMARY                      | PRIMARY | 4       | dashboard.m.clusterId     |      1 |   100.00 | NULL                                         |
+----+-------------+-------+------------+--------+------------------------------+---------+---------+---------------------------+--------+----------+----------------------------------------------+

pinger_history表大​​约包含 483,750,000行,而pinger_history_updown包含大约 115,520行。其他表比较小(少于300行)。

如果有人在优化查询或调试瓶颈方面有经验,那么将不胜感激所有帮助。

编辑:

我将缺少的order by h.id desc添加到查询中,并将pinger_history设为查询中的第一张表。

以下是pinger_history和pinger_history_updown的创建表查询:

pinger_history:

mysql> show create table pinger_history;
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table          | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| pinger_history | CREATE TABLE `pinger_history` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `deviceId` int(10) unsigned NOT NULL,
  `machineIpId` int(10) unsigned NOT NULL,
  `minRoundTripTime` decimal(6,1) unsigned DEFAULT NULL,
  `maxRoundTripTime` decimal(6,1) unsigned DEFAULT NULL,
  `averageRoundTripTime` decimal(6,1) unsigned DEFAULT NULL,
  `packetLossRatio` decimal(3,2) unsigned DEFAULT NULL,
  `timePinged` datetime NOT NULL,
  `status` enum('Up','Unstable','Down') DEFAULT NULL,
  `firstOppositeStatusPingResultId` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `deviceId` (`deviceId`),
  KEY `machineIpId` (`machineIpId`),
  KEY `timePinged` (`timePinged`),
  KEY `firstOppositeStatusPingResultId` (`firstOppositeStatusPingResultId`),
  CONSTRAINT `pinger_history_ibfk_2` FOREIGN KEY (`machineIpId`) REFERENCES `pinger_machine_ip_addresses` (`id`),
  CONSTRAINT `pinger_history_ibfk_4` FOREIGN KEY (`deviceId`) REFERENCES `pinger_devices` (`id`) ON DELETE CASCADE,
  CONSTRAINT `pinger_history_ibfk_5` FOREIGN KEY (`firstOppositeStatusPingResultId`) REFERENCES `pinger_history` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB AUTO_INCREMENT=483833283 DEFAULT CHARSET=utf8mb4 |
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

pinger_history_updown:

+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table                 | Create Table                                                                                                                                                                                                                                                                                                                                    |
+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| pinger_history_updown | CREATE TABLE `pinger_history_updown` (
  `pingResultId` int(10) unsigned NOT NULL,
  `notified` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`pingResultId`),
  CONSTRAINT `pinger_history_updown_ibfk_1` FOREIGN KEY (`pingResultId`) REFERENCES `pinger_history` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 |
+-----------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

编辑2:

这是pinger_history的show index的输出:

mysql> show index from pinger_history;
+----------------+------------+---------------------------------+--------------+---------------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table          | Non_unique | Key_name                        | Seq_in_index | Column_name                     | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------------+------------+---------------------------------+--------------+---------------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| pinger_history |          0 | PRIMARY                         |            1 | id                              | A         |   443760800 |     NULL | NULL   |      | BTREE      |         |               |
| pinger_history |          1 | deviceId                        |            1 | deviceId                        | A         |      288388 |     NULL | NULL   |      | BTREE      |         |               |
| pinger_history |          1 | machineIpId                     |            1 | machineIpId                     | A         |       71598 |     NULL | NULL   |      | BTREE      |         |               |
| pinger_history |          1 | timePinged                      |            1 | timePinged                      | A         |    38041236 |     NULL | NULL   |      | BTREE      |         |               |
| pinger_history |          1 | firstOppositeStatusPingResultId |            1 | firstOppositeStatusPingResultId | A         |        8973 |     NULL | NULL   | YES  | BTREE      |         |               |
+----------------+------------+---------------------------------+--------------+---------------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

修改3:

这是我添加straight_join时的说明输出:

请注意,使用straight_join进行查询将花费近2分钟,而如果不使用查询,则大约需要36秒。

+----+-------------+-------+------------+--------+------------------------------+----------+---------+-------------------------+--------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys                | key      | key_len | ref                     | rows   | filtered | Extra       |
+----+-------------+-------+------------+--------+------------------------------+----------+---------+-------------------------+--------+----------+-------------+
|  1 | SIMPLE      | h     | NULL       | ref    | PRIMARY,deviceId,machineIpId | deviceId | 4       | const                   | 344062 |   100.00 | Using where |
|  1 | SIMPLE      | ud    | NULL       | eq_ref | PRIMARY                      | PRIMARY  | 4       | dashboard.h.id          |      1 |   100.00 | Using index |
|  1 | SIMPLE      | i     | NULL       | eq_ref | PRIMARY,machineId            | PRIMARY  | 4       | dashboard.h.machineIpId |      1 |   100.00 | NULL        |
|  1 | SIMPLE      | m     | NULL       | eq_ref | PRIMARY,clusterId            | PRIMARY  | 4       | dashboard.i.machineId   |      1 |   100.00 | Using where |
|  1 | SIMPLE      | c     | NULL       | eq_ref | PRIMARY                      | PRIMARY  | 4       | dashboard.m.clusterId   |      1 |   100.00 | NULL        |
+----+-------------+-------+------------+--------+------------------------------+----------+---------+-------------------------+--------+----------+-------------+

4 个答案:

答案 0 :(得分:0)

在以下列上创建索引

pingResultId ,machineIpId ,clusterId .pinger_clusters .id,
pinger_machine_ip_addresses.id,pinger_history.id,pinger_machines.id,deviceId ,
pinger_history_updown.pingResultId 

建立索引将减少获取数据所需的时间

答案 1 :(得分:0)

我将添加“ STRAIGHT_JOIN”关键字,并将pinger_history置于第一位置。然后,通过DeviceID在pinger_history上包含一个索引,以优化WHERE子句。您的其他表可能已经暗示了它们各自的ID密钥上的索引,应该很好。 STRAIGHT_JOIN子句告诉MySQL以我给您的表/联接顺序运行查询,不要暗示其他事情。

select STRAIGHT_JOIN
      c.id as clusterId,
      c.name as cluster,
      m.id as machineId,
      m.label as machine,
      h.id as pingResultId,
      h.timePinged as `timestamp`,
      h.status
   from 
      pinger_history h 
         join pinger_history_updown ud
            on h.id = ud.pingResultId
         join pinger_machine_ip_addresses i 
            on h.machineIpId = i.id
               join pinger_machines m 
                  on i.machineId = m.id
                  join pinger_clusters c 
                     on m.clusterId = c.id
   where 
      h.deviceId = ?
   order by
      h.id desc
   limit 100

由于您确实想要最新的记录,因此我肯定会在(DeviceID,ID)的pinger_history表上建立索引-仅更改您现有的DeviceID密钥并将其更改为(DeviceID,ID)

这样,首先优化了WHERE子句以获取设备ID记录。通过将ID作为索引的一部分,但将其置于第二位置,ORDER by可以利用该ID为您获取最新的ID。

答案 2 :(得分:0)

您写道,您的查询会获取设备的最新100个事件,但SQL中没有ORDER BY子句。

在查询中添加ORDER BY h.id DESC,并在(devideId,id)字段上创建复合索引。

答案 3 :(得分:0)

方案A:摆脱pinger_history_updown并将notified移到pinger_history中。也许增加status来表示“ CameUp”和“ WentDown”。优点:由于可以使用INDEX(deviceId),因此查询速度更快。缺点:它使pinger_history a little bigger;将列添加到巨大的表将需要时间。

方案B:将deviceId添加到pinger_history_updown并拥有INDEX(deviceID, pingResultId)。优点:查询更快。缺点:冗余数据(deviceid)被皱了皱眉。

计划C:添加索引提示以强制执行从pinger_history开始。骗局:“今天有用的东西明天可能会受伤。” ({STRAIGHT_JOIN经过测试,发现比较慢。)

计划D:看看每个表的ANALYZE TABLE是否有帮助。优点:快速又便宜。缺点:可能无济于事。

方案E:更改为ORDER BY deviceId DESC, id DESC。优点:便宜又容易尝试。缺点:可能无济于事。

计划F:在pinger_history中更改

      PRIMARY KEY (`id`),
      KEY `deviceId` (`deviceId`),

      PRIMARY KEY(deviceId, id),
      KEY(id)

这将使所需的行“聚集”更好。优点:更快。骗局:ALTER TABLE会花费很长的时间。

计划G:假设这是一个爆炸内爆问题,并将LIMIT移到派生表中:

select  c.id as clusterId, c.name as cluster, m.id as machineId,
        m.label as machine, h2.id as pingResultId, h2.timePinged as `timestamp`,
        h2.status
    FROM  
    (        -- "derived table"
        SELECT  ud1.pingResultId
            FROM  pinger_history_updown AS ud1
            JOIN  pinger_history AS h1  ON ud1.pingResultId = h1.id
            WHERE  h1.deviceId = ?
            ORDER BY  ud1.pingResultId
            LIMIT  100    -- only needed here
    ) AS ud2
    JOIN  pinger_history AS h2  ON ud2.pingResultId = h2.id
    join  pinger_machine_ip_addresses i  ON h.machineIpId = i.id
    join  pinger_machines m  ON i.machineId = m.id
    join  pinger_clusters c  ON m.clusterId = c.id
    order by  h2.id desc   -- Yes, this is repeated

专业版:可以更好地利用“覆盖” INDEX(deviceId),尤其是与计划B合并时。

摘要:以D和E开头。