我正在 MySQL 中为 Grafana 仪表板编写查询,但仅此特定查询的性能非常差。 这是查询:
SELECT
br.reading
,CONCAT(ino.project,"_",SUBSTRING_INDEX(br.sensor,"_",-1)) AS new_metric
,ino.vessel
,ADDTIME(DATE('2023-01-01'),-TIMEDIFF(ino.date,br.datetime)) AS norm_date
,br.sensor
FROM db.milestones br
INNER JOIN db.projects ino
ON br.datetime BETWEEN ino.date AND ino.enddate
WHERE project IN ( 'project1','project2','project3','project4' )
AND br.sensor LIKE CONCAT(ino.vessel , '%')
EXPLAIN
输出:
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
| -- | ----------- | ----- | ---------- | ---- | ------------- | --- | ------- | --- | -------- | -------- | ---------------------------------------------- |
| 1 | SIMPLE | ino | | ALL | | | | | 5 | 100.00 | |
| 1 | SIMPLE | br | | ALL | idx_datetime | | | | 31865381 | 1.23 | Range checked for each record (index map: 0x2) |
创建表:
CREATE TABLE `projectdb` (
`project` varchar(10) DEFAULT NULL,
`date` datetime DEFAULT NULL,
`enddate` datetime DEFAULT ((`date` + interval 3 day)),
`vessel` varchar(45) DEFAULT NULL,
UNIQUE KEY `project_UNIQUE` (`project`),
KEY `idx_batch` (`project`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `milestones` (
`id` int NOT NULL AUTO_INCREMENT,
`datetime` datetime DEFAULT CURRENT_TIMESTAMP,
`sensor` varchar(20) DEFAULT NULL,
`value` decimal(10,4) DEFAULT NULL,
`monitor` tinyint DEFAULT NULL,
PRIMARY KEY (`idbioreactors`),
KEY `idx_datetime` (`datetime` DESC) USING BTREE,
KEY `idx_sensors` (`sensor`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=31649068 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
索引:
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
| ---------- | ---------- | -------------- | ------------ | ----------- | --------- | ----------- | -------- | ------ | ---- | ---------- | ------- | ------------- | ------- | ---------- |
| projectdb | 0 | project_UNIQUE | 1 | project | A | 4 | | | YES | BTREE | | | YES | |
| projectdb | 1 | idx_project | 1 | project | A | 5 | | | YES | BTREE | | | YES | |
| milestones | 0 | PRIMARY | 1 | id | A | 31648118 | | | | BTREE | | | YES | |
| milestones | 1 | idx_datetime | 1 | datetime | D | 1723411 | | | YES | BTREE | | | YES | |
| milestones | 1 | idx_sensors | 1 | sensor | A | 21924 | | | YES | BTREE | | | YES | |
projectdb 表示例:
| project | date | enddate | vessel |
| --------- | ---------------- | ---------------- | ------ |
| Project 1 | 24/11/2023 17:30 | 27/11/2023 17:30 | V1 |
| Project 2 | 17/11/2023 19:50 | 20/11/2023 19:50 | V1 |
| Project 3 | 27/10/2023 16:00 | 30/10/2023 16:00 | V2 |
里程碑表样本
| id | datetime | sensor | reading |
| -------- | ---------------- | -------- | ------- |
| 22117821 | 10/10/2023 19:20 | V1_FT001 | 100 |
| 22118005 | 10/10/2023 19:21 | V2_FT001 | 120 |
| 22118189 | 10/10/2023 19:23 | V1_FT001 | 100 |
| 22117835 | 10/10/2023 19:20 | V3_FT001 | 105 |
查看执行计划,很明显
db.milestones.sensor
和db.milestones.datetime
上的索引没有被使用。这意味着全表扫描需要遍历全部 3600 万行,这解释了性能不佳的原因。我假设因为这是时间序列数据,所以我有 81 个传感器一遍又一遍地重复,使得索引不太可能被使用。
有没有办法修改此查询以使其始终使用索引,或者如果我想提高查询速度,是否需要采取完全不同的方法?
我尝试在第一个连接中使用子查询在末尾包含
WHERE
子句,以在连接之前过滤掉不必要的数据。这种方法最终表现最差,耗时长达 2 分钟。
我尝试创建 db.milestones 表的视图来包含与
WHERE
子句进行比较的子字符串,因此我可以使用 =
而不是 LIKE
。这也没有达到预期,表现也很糟糕。
我尝试使用
FORCE INDEX
来使用索引,但这不起作用。
有趣的是,如果我减少
WHERE project IN...
中的条件,则使用 db.milestone.sensor
上的索引,并且处理时间会好得多。
目前,你在
project
列上有两个索引,其中一个是唯一的,没有PK。您应该删除现有的两个索引并在 project
上添加 PK。
合理表现的关键是
(sensor, datetime)
上的复合键以及vessel
和sensor
之间的具体关系。
添加一个简单的连接表(
vessel_sensor
)将使世界变得不同:
船只 | 传感器 |
---|---|
V1 | V1_FT001 |
V2 | V2_FT001 |
V3 | V3_FT001 |
综合指数:
ALTER TABLE milestones ADD INDEX idx_sensor_datetime (sensor, datetime);
然后将连接表添加到现有查询中:
SELECT
m.reading,
CONCAT(p.project, '_', SUBSTRING_INDEX(m.sensor, '_', -1)) AS new_metric,
p.vessel,
ADDTIME(DATE('2023-01-01'), -TIMEDIFF(p.date, m.datetime)) AS norm_date,
m.sensor
FROM projects p
JOIN vessel_sensor vs
ON vs.vessel = p.vessel
JOIN milestones m
ON m.sensor = vs.sensor
AND m.datetime BETWEEN p.date AND p.enddate
WHERE p.project IN ( 'project1', 'project2', 'project3', 'project4' );