优化 Mysql 查询左连接
Optimization Mysql Query Left Join
我们想通过以下查询将 calibration_data 的条目映射到校准数据。但是在我看来这个查询的持续时间太长了 (>24h)。
有没有优化的可能?
我们现在根据需要添加了测试更多索引,但这对持续时间没有任何影响。
[编辑]
硬件应该不是最大的瓶颈
- 128 GB 内存
- 1TB SSD RAID 5
- 32 核
解释结果
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+------------------------------------------------+
| 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
进行某种计算 而不是 。让我知道是否是这种情况;我会继续我的想法。
我们想通过以下查询将 calibration_data 的条目映射到校准数据。但是在我看来这个查询的持续时间太长了 (>24h)。
有没有优化的可能? 我们现在根据需要添加了测试更多索引,但这对持续时间没有任何影响。
[编辑]
硬件应该不是最大的瓶颈
- 128 GB 内存
- 1TB SSD RAID 5
- 32 核
解释结果
+----+-------------+-------+------------+------+---------------+------+---------+------+---------+----------+------------------------------------------------+
| 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
进行某种计算 而不是 。让我知道是否是这种情况;我会继续我的想法。