优化 Mysql 查询左连接

Optimization Mysql Query Left Join

我们想通过以下查询将 calibration_data 的条目映射到校准数据。但是在我看来这个查询的持续时间太长了 (>24h)。

有没有优化的可能? 我们现在根据需要添加了测试更多索引,但这对持续时间没有任何影响。

[编辑]

硬件应该不是最大的瓶颈

解释结果

+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra                                          |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+------------------------------------------------+
|  1 | SIMPLE      | cal   | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2009 |   100.00 | Using temporary; Using filesort                |
|  1 | SIMPLE      | m     | NULL       | ALL  | visit         | NULL | NULL    | NULL | 3082466 |   100.00 | Range checked for each record (index map: 0x1) |
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+------------------------------------------------+

查询时间过长:

Insert into knn_data (SELECT cal.X           AS X, 
        cal.Y           AS Y, 
        cal.BeginTime   AS BeginTime, 
        cal.EndTime     AS EndTime, 
        avg(m.dbm_ant)  AS avg_dbm_ant, 
        m.ant_id        AS ant_id, 
        avg(m.location) avg_location, 
        count(*)        AS count, 
        m.visit 
 FROM   calibration cal 
        LEFT join calibration_data m
          ON m.visit BETWEEN cal.BeginTime AND cal.EndTime 
 GROUP  BY cal.X, 
           cal.Y, 
           cal.BeginTime, 
           cal. BeaconId, 
           m.ant_id,
           m.macHash,
           m.visit; 

Table knn_data:

    CREATE TABLE `knn_data` (
  `X` int(11) NOT NULL,
  `Y` int(11) NOT NULL,
  `BeginTime` datetime NOT NULL,
  `EndTIme` datetime NOT NULL,
  `avg_dbm_ant` float DEFAULT NULL,
  `ant_id` int(11) NOT NULL,
  `avg_location` float DEFAULT NULL,
  `count` int(11) DEFAULT NULL,
  `visit` datetime NOT NULL,
  PRIMARY KEY (`ant_id`,`visit`,`X`,`Y`,`BeginTime`,`EndTIme`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Table校准

BeaconId, X, Y, BeginTime, EndTime
41791, 1698, 3944, 2016-11-12 22:44:00, 2016-11-12 22:49:00


CREATE TABLE `calibration` (
  `BeaconId` int(11) DEFAULT NULL,
  `X` int(11) DEFAULT NULL,
  `Y` int(11) DEFAULT NULL,
  `BeginTime` datetime DEFAULT NULL,
  `EndTime` datetime DEFAULT NULL,
  KEY `x,y` (`X`,`Y`),
  KEY `x` (`X`),
  KEY `y` (`Y`),
  KEY `BID` (`BeaconId`),
  KEY `beginTime` (`BeginTime`),
  KEY `x,y,beg,bid` (`X`,`Y`,`BeginTime`,`BeaconId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Table calibration_data

macHash, visit, dbm_ant, ant_id, mac, isRand, posX, posY, sources, ip, dayOfMonth, location, am, ar
'f5:dc:7d:73:2d:e9', '2016-11-12 22:44:00', '-87', '381', 'f5:dc:7d:73:2d:e9', NULL, NULL, NULL, NULL, NULL, '12', '18.077636300207715', 'inradius_41791', NULL


CREATE TABLE `calibration_data` (
  `macHash` varchar(100) COLLATE utf8_bin NOT NULL,
  `visit` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `dbm_ant` int(3) NOT NULL,
  `ant_id` int(11) NOT NULL,
  `mac` char(17) COLLATE utf8_bin DEFAULT NULL,
  `isRand` tinyint(4) DEFAULT NULL,
  `posX` double DEFAULT NULL,
  `posY` double DEFAULT NULL,
  `sources` int(2) DEFAULT NULL,
  `ip` int(10) unsigned DEFAULT NULL,
  `dayOfMonth` int(11) DEFAULT NULL,
  `location` varchar(80) COLLATE utf8_bin DEFAULT NULL,
  `am` varchar(300) COLLATE utf8_bin DEFAULT NULL,
  `ar` varchar(300) COLLATE utf8_bin DEFAULT NULL,
  KEY `visit` (`visit`),
  KEY `macHash` (`macHash`),
  KEY `ant, time` (`dbm_ant`,`visit`),
  KEY `beacon` (`am`),
  KEY `ant_id` (`ant_id`),
  KEY `ant,mH,visit` (`ant_id`,`macHash`,`visit`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

这是 "range" 查询中的一个令人讨厌的经典查询:优化器不使用您的索引并最终进行完整的 table 扫描。在您的解释计划中,您可以在 type=ALL.

列中看到这一点

理想情况下,您应该 type=range 和键列中的内容

一些想法:


我怀疑从

改变你的关节
ON m.visit BETWEEN cal.BeginTime AND cal.EndTime 

ON m.visit >= cal.BeginTime AND m.visit <= cal.EndTime

会起作用,但还是试一试。


在两个 table 上触发 ANALYSE TABLE。这将更新您的 table 上的统计数据,并可能帮助优化器做出正确的决定(即使用索引)


将查询更改为此也可能有助于强制优化器使用索引:

Insert into knn_data (SELECT cal.X           AS X, 
        cal.Y           AS Y, 
        cal.BeginTime   AS BeginTime, 
        cal.EndTime     AS EndTime, 
        avg(m.dbm_ant)  AS avg_dbm_ant, 
        m.ant_id        AS ant_id, 
        avg(m.location) avg_location, 
        count(*)        AS count, 
        m.visit 
 FROM   calibration cal 
        LEFT join calibration_data m
          ON m.visit >= cal.BeginTime 
 WHERE m.visit <= cal.EndTime 
 GROUP  BY cal.X, 
           cal.Y, 
           cal.BeginTime, 
           cal. BeaconId, 
           m.ant_id,
           m.macHash,
           m.visit; 

我就是这么想的...

一次性任务?那就不要紧了?加载此数据后,您会增量 每天更新 "summary table" 吗?

缩小数据类型 -- 庞大的数据需要更长的时间来处理。示例:4 字节 INT DayOfMonth 可能是 1 字节 TINYINT UNSIGNED.

您正在将 TIMESTAMP 移动到 DATETIME。这可能会或可能不会像您预期的那样工作。

INT UNSIGNED 适用于 IPv4,但您不能将 IPv6 放入其中。

COUNT(*)大概不需要4字节INT;查看较小的变体。

在适当的地方使用UNSIGNED

A mac-address 按照您的方式占用 19 个字节;它可以很容易地转换为 to/from 一个 6 字节 BINARY(6)。参见 REPLACE()UNHEX()HEX()

innodb_buffer_pool_size的设置是什么?对于您拥有的大 RAM,它可能约为 100G。

时间范围是否重叠?如果没有,请利用它。另外,不要在 PRIMARY KEY 中包含不必要的列,例如 EndTime.

GROUP BY 列的顺序与 knn_data 的 PRIMARY KEY 相同;这将避免 INSERT.

期间的大量块拆分

最大的问题是 calibration_data 中没有有用的索引,所以 JOIN 必须一次又一次地进行完整的 table 扫描!对 3M 行的 extimated 2K 扫描!让我专注于那个问题...

WHERE x BETWEEN start AND end没有好的办法,因为MySQL不知道日期时间范围是否重叠。在这种情况下没有真正的治愈方法,所以让我以不同的方式处理它......

开始和结束是'regular'?比如每小时?当然,我们可以对 BETWEEN 进行某种计算 而不是 。让我知道是否是这种情况;我会继续我的想法。