如何按速度优化 mysql 的日期组?
How can I optimize mysql's date group by speed?
CREATE TABLE `device_m1000` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`sensor_id` MEDIUMINT(9) NOT NULL,
`ctime` DATETIME NOT NULL,
`now_data` FLOAT(12) NOT NULL,
`total_data` FLOAT(12) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `sensor_id` (`sensor_id`) USING BTREE,
INDEX `ctime` (`ctime`) USING BTREE) COLLATE='utf8_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=296533;
上面table中有30万条数据。
total_data 列总是出现在累计使用情况中。 (一个总是增加的结构)
如果我想知道“今天”的用法,我根据今天的日期取最小值和最大值并减去它。
尝试sql
SELECT ROUND((max.v - min.v), 2) total
FROM (SELECT
DATE(ctime) `date`,
sum(total_data) v
FROM device_m1000
WHERE ( sensor_id, ctime ) IN (SELECT sensor_id,
MAX(ctime) AS dt
FROM device_m1000
WHERE ctime >= '2020-11-23 06:30' and ctime < '2020-11-24 06:30'
GROUP BY sensor_id
ORDER BY null)
group by `date`) max
INNER JOIN (SELECT
DATE(ctime) `date`,
sum(total_data) v
FROM device_m1000
WHERE ( sensor_id, ctime ) IN (SELECT sensor_id,
MIN(ctime) AS dt
FROM device_m1000
WHERE ctime >= '2020-11-23 06:30' and ctime < '2020-11-24 06:30'
GROUP BY sensor_id
ORDER BY null)
group by `date`) min
ON min.`date` = max.`date`;
使用以下查询获取数据。
但是,它在 max(ctime) 的代码中只是延迟了 10 多秒。
我该如何优化它?
我了解到您想要每个传感器今天最迟和最早 total_data
之间的差异。如果是这样,您可以像这样使用 window 函数:
select sensor_id, sum(case when rn_desc = 1 then total_data else - total_data end) as total_diff
from (
select d.*,
row_number() over(partition by sensor_id order by ctime) rn_asc,
row_number() over(partition by sensor_id order by ctime desc) rn_desc
from device_m1000
where ctime >= current_date and ctime < current_date + interval 1 day
) t
where 1 in (rn_asc, rn_desc)
group by sensor_id
实际上如果 total_data
总是增加,这更简单(并且适用于所有 MySQL 版本):
select sensor_id, max(total_data) - min(total_data) as total_diff
from device_m1000
where ctime >= current_date and ctime < current_date + interval 1 day
group by sensor_id
那我推荐如下索引:(ctime, sensor_id, total_data)
.
- “行构造函数”表现不佳。 (
WHERE (a,b) IN ...
)
FLOAT(12)
不给你12个有效数字,你只得到大约7个。
id
有用吗?还是 (sensor_id, ctime)
是独一无二的?如果它是唯一的,将其设为 PRIMARY KEY
并去掉 id
.
- PK不了就
INDEX(sensor_id, ctime)
.
回到你的问题。放弃将小计放入每一行的尝试。实际上,任何方法每秒都将执行超过 10 亿次操作——因为必须为每个传感器汇总当天的大约 30,000 个项目。
而是制作一个单独的“摘要 table”,其中包含当天的日期、传感器和总计。然后,当你 INSERT
进入细节时 table (就像你拥有的那样),也做
INSERT INTO daily_summary (date, sensor, tot)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
tot = VALUES(tot) + ?
并使用 DOUBLE
作为 tot
。并且有 PRIMARY KEY(sensor, date)
这避免了减法并提高了效率。
有关摘要的更多信息 tables:http://mysql.rjweb.org/doc.php/summarytables
如果您需要每小时小计,则将其设为每小时 table 而不是每天。您可以通过从每小时摘要中添加 24 行来有效地获得每日金额 table.
CREATE TABLE `device_m1000` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`sensor_id` MEDIUMINT(9) NOT NULL,
`ctime` DATETIME NOT NULL,
`now_data` FLOAT(12) NOT NULL,
`total_data` FLOAT(12) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `sensor_id` (`sensor_id`) USING BTREE,
INDEX `ctime` (`ctime`) USING BTREE) COLLATE='utf8_unicode_ci' ENGINE=InnoDB AUTO_INCREMENT=296533;
上面table中有30万条数据。 total_data 列总是出现在累计使用情况中。 (一个总是增加的结构) 如果我想知道“今天”的用法,我根据今天的日期取最小值和最大值并减去它。
尝试sql
SELECT ROUND((max.v - min.v), 2) total
FROM (SELECT
DATE(ctime) `date`,
sum(total_data) v
FROM device_m1000
WHERE ( sensor_id, ctime ) IN (SELECT sensor_id,
MAX(ctime) AS dt
FROM device_m1000
WHERE ctime >= '2020-11-23 06:30' and ctime < '2020-11-24 06:30'
GROUP BY sensor_id
ORDER BY null)
group by `date`) max
INNER JOIN (SELECT
DATE(ctime) `date`,
sum(total_data) v
FROM device_m1000
WHERE ( sensor_id, ctime ) IN (SELECT sensor_id,
MIN(ctime) AS dt
FROM device_m1000
WHERE ctime >= '2020-11-23 06:30' and ctime < '2020-11-24 06:30'
GROUP BY sensor_id
ORDER BY null)
group by `date`) min
ON min.`date` = max.`date`;
使用以下查询获取数据。 但是,它在 max(ctime) 的代码中只是延迟了 10 多秒。 我该如何优化它?
我了解到您想要每个传感器今天最迟和最早 total_data
之间的差异。如果是这样,您可以像这样使用 window 函数:
select sensor_id, sum(case when rn_desc = 1 then total_data else - total_data end) as total_diff
from (
select d.*,
row_number() over(partition by sensor_id order by ctime) rn_asc,
row_number() over(partition by sensor_id order by ctime desc) rn_desc
from device_m1000
where ctime >= current_date and ctime < current_date + interval 1 day
) t
where 1 in (rn_asc, rn_desc)
group by sensor_id
实际上如果 total_data
总是增加,这更简单(并且适用于所有 MySQL 版本):
select sensor_id, max(total_data) - min(total_data) as total_diff
from device_m1000
where ctime >= current_date and ctime < current_date + interval 1 day
group by sensor_id
那我推荐如下索引:(ctime, sensor_id, total_data)
.
- “行构造函数”表现不佳。 (
WHERE (a,b) IN ...
) FLOAT(12)
不给你12个有效数字,你只得到大约7个。id
有用吗?还是(sensor_id, ctime)
是独一无二的?如果它是唯一的,将其设为PRIMARY KEY
并去掉id
.- PK不了就
INDEX(sensor_id, ctime)
.
回到你的问题。放弃将小计放入每一行的尝试。实际上,任何方法每秒都将执行超过 10 亿次操作——因为必须为每个传感器汇总当天的大约 30,000 个项目。
而是制作一个单独的“摘要 table”,其中包含当天的日期、传感器和总计。然后,当你 INSERT
进入细节时 table (就像你拥有的那样),也做
INSERT INTO daily_summary (date, sensor, tot)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE
tot = VALUES(tot) + ?
并使用 DOUBLE
作为 tot
。并且有 PRIMARY KEY(sensor, date)
这避免了减法并提高了效率。
有关摘要的更多信息 tables:http://mysql.rjweb.org/doc.php/summarytables
如果您需要每小时小计,则将其设为每小时 table 而不是每天。您可以通过从每小时摘要中添加 24 行来有效地获得每日金额 table.