如何优化我的 mysql 请求以使用大型数据库
Posted
技术标签:
【中文标题】如何优化我的 mysql 请求以使用大型数据库【英文标题】:How to optimize my mysql request to work with large database 【发布时间】:2019-12-02 15:33:13 【问题描述】:我正在使用 php/mysql(mariaDB) 开发一个项目,该项目使用一些数据作为输入生成统计信息。填充 DB 的系统是一个电力装置,特别是警报系统。
我的数据库有三个字段:DeviceTime
、VariableName
和 alarmState
alarmState 字段可以有 2 种可能的状态:Normal
(当警报响起时)和 Active
(当警报处于活动状态时)。
我想在警报出现的时间 (DeviceTime) (Active State) 和警报关闭的时间 (DeviceTime) (Normal state) 之间进行统计
今天我提出了一个可以正常工作的请求,但仅包含少量数据。 当我用所有数据(大约 48k 行)测试请求时,请求花费的时间太长,一段时间后 mysql 崩溃。
这是我的请求,适用于少量数据
select k.deviceTime as stime, k.variableName as svar, k.alarmState as sstate, i.deviceTime, i.variableName, i.alarmState, timediff(i.deviceTime, k.deviceTime) as diff
from imports k
join imports i on i.variableName = k.variableName
and
i.deviceTime = (select t.deviceTime
from imports t
where t.variableName = k.variableName and
t.deviceTime > k.deviceTime and
t.alarmState ='NORMAL'
order by t.deviceTime limit 1
)
where k.alarmState = 'ACTIVE'
这是我的数据表:
id deviceTime variableName alarmState
1 2019-07-11T10:05:24.482 B1.d_07QFA11AN001XB08 ACTIVE
2 2019-07-11T10:05:24.282 B1.d_07QFA11AN001XB08 NORMAL
3 2019-07-11T10:05:15.409 G1.PTUR-38-T.228.52 ACTIVE
4 2019-07-11T10:03:51.409 G1.PTUR-38-T.228.52 NORMAL
5 2019-07-11T10:03:37.409 G1.PTUR-38-T.228.52 ACTIVE
6 2019-07-11T10:03:09.409 G1.PTUR-38-T.228.52 NORMAL
7 2019-07-11T10:02:55.409 G1.PTUR-38-T.228.52 ACTIVE
8 2019-07-11T09:52:06.415 B1.d_07QFA11AN001XB08 ACTIVE
9 2019-07-11T09:52:06.214 B1.d_07QFA11AN001XB08 NORMAL
10 2019-07-11T09:51:06.403 B1.d_07QFA11AN001XB08 ACTIVE
数据量少的结果:
stime svar sstate deviceTime variableName alarmState diff
2019-07-11T09:52:06.415 B1.d_07QFA11AN001XB08 ACTIVE 2019-07-11T10:05:24.282 B1.d_07QFA11AN001XB08 NORMAL 00:13:17
2019-07-11T10:03:37.409 G1.PTUR-38-T.228.52 ACTIVE 2019-07-11T10:03:51.409 G1.PTUR-38-T.228.52 NORMAL 00:00:14
2019-07-11T10:02:55.409 G1.PTUR-38-T.228.52 ACTIVE 2019-07-11T10:03:09.409 G1.PTUR-38-T.228.52 NORMAL 00:00:14
2019-07-11T09:51:06.403 B1.d_07QFA11AN001XB08 ACTIVE 2019-07-11T09:52:06.214 B1.d_07QFA11AN001XB08 NORMAL 00:00:59
这正是我想要的结果,但是如果有人有优化这个请求的想法,或者另一种方法来构建一个可以返回alarmState
和对应的variableName
之间的时间差的请求。
编辑:
我的 MariaDB 版本是10.4.6-MariaDB
这是表结构
create table imports
(
id bigint unsigned auto_increment
primary key,
deviceTime varchar(255) not null,
variableName varchar(255) not null,
alarmState varchar(255) null,
created_at timestamp null,
updated_at timestamp null
);
还有Explain query
id select_type table type possible_key key key_len ref rows Extra
1 PRIMARY k ALL <null> <null> <null> <null> 44679 Using where; Using temporary; Using filesort
1 PRIMARY i ALL <null> <null> <null> <null> 44679 Using where; Using join buffer (flat, BNL join)
2 DEPENDENT SUBQUERY t ALL <null> <null> <null> <null> 44679 Using where; Using filesort
编辑2
我将deviceTime
列的类型更改为DATETIME
。我创建了这样的索引
create index imports_alarmstate_index
on imports (alarmState);
create index imports_devicetime_index
on imports (deviceTime);
create index imports_variablename_index
on imports (variableName);
我将查询修改为使用MIN()
而不是mysql Order BY ... Limit 1
。
现在我的查询看起来像
select k.deviceTime as stime,
k.variableName as svar,
k.alarmState as sstate,
i.deviceTime,
i.variableName,
i.alarmState,
timestampdiff (second, i.deviceTime, k.deviceTime) as diff
from imports k
join imports i on i.variableName = k.variableName and
i.deviceTime = (select MIN(t.deviceTime)
from imports t
where t.variableName = k.variableName and
t.deviceTime > k.deviceTime and
t.alarmState ='NORMAL'
)
where k.alarmState <> 'NORMAL'
我使用timestampdiff()
而不是datediff()
,因为时间戳格式更易于订购。
我的 where 条件 k.alarmState <> 'NORMAL'
发生了变化,因为有时 alarmState
可以在特定条件下采用另一种状态,但是这种新状态就像 Active
状态
这是我的新EXPLAIN
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY k ALL imports_variablename_index,imports_alarmstate_index <null> <null> <null> 45474 50 Using where
1 PRIMARY i ref imports_devicetime_index,imports_variablename_index imports_devicetime_index 5 func 1 100 Using where
2 DEPENDENT SUBQUERY t ref imports_devicetime_index,imports_variablename_index,imports_alarmstate_index imports_variablename_index 1022 Alarms.k.variableName 29 50 Using where
当我执行查询时,我得到了
34567 rows retrieved starting from 1 in 3 m 26 s 135 ms (execution: 158 ms, fetching: 3 m 25 s 977 ms)
我觉得 3 分钟有点长,不是吗?还有其他优化或建议吗?
谢谢!
【问题讨论】:
哪个 MySQL/MariaDB 版本?SELECT VERSION();
另外关于性能的问题还应该包括查询中涉及的每个表的表结构(SHOW CREATE TABLE table
)。还有一个EXPLAIN query
输出
看起来像一个“groupwise-max”问题。请参阅添加的标签进行优化。
如前所述,为所有 3 列添加覆盖索引(即,1 个索引包含所有 3 列)。通常 MySQL 不会组合单独的索引,因此您需要 1 个索引来覆盖所有列。并且删除子查询可能会有很大帮助(它必须为它尝试加入的每一行执行该子查询)。
【参考方案1】:
索引有很大的不同,
但是有可能在没有子查询的情况下重写您的查询。
如果我正确地阅读了你的 SQL,这样的事情会起作用。
SELECT k.deviceTime as stime,
k.variableName as svar,
k.alarmState as sstate,
i.deviceTime,
i.variableName,
i.alarmState,
timediff(i.deviceTime, k.deviceTime) as diff
FROM imports k
INNER JOIN imports i
ON i.variableName = k.variableName
AND i.deviceTime > k.deviceTime
AND t.alarmState ='NORMAL'
LEFT OUTER JOIN imports t
ON t.variableName = k.variableName
AND t.deviceTime > k.deviceTime
AND t.deviceTime < i.deviceTime
AND t.alarmState ='NORMAL'
WHERE k.alarmState = 'ACTIVE'
AND t.id IS NULL
这是一个连接以查找未来某个时间的同一变量的未来导入,以及一个 OUTER JOIN 以查找介于“活动”和“正常”警报时间之间的任何内容。然后在 OUTER JOIN 中找到任何结果的行将被忽略。
【讨论】:
感谢您的询问,但表现不佳。执行时间很长(如 30 分钟 +)。我不知道为什么。我还使索引和查询工作得更好,但还没有 @Cripsii - 你添加了哪些索引?字段警报状态、变量名和设备时间(按此顺序)的覆盖索引可能会产生很大的不同。【参考方案2】:尝试在(alarmState, variableName, deviceTime)
上创建复合索引。
为什么?你有这个 WHERE 子句
where t.variableName = k.variableName
and t.deviceTime > k.deviceTime
and t.alarmState ='NORMAL'
这会在variableName
和alarmState
上进行相等匹配,然后在deviceTime
上进行范围匹配。相等匹配首先出现在索引中,然后是范围。
并且,尝试重构您的依赖子查询以使用 MIN()
而不是 ORDER BY ... LIMIT 1
像这样:
select MIN(t.deviceTime)
from imports t
where t.variableName = k.variableName and
t.deviceTime > k.deviceTime and
t.alarmState ='NORMAL'
那可以使用所谓的loose index scan来查找下一次。
编辑从您的编辑中我看到您的deviceTime
列是varchar(255)
。对于您正在尝试做的事情,这是非常低效的。您可以改用TIMESTAMP
或DATETIME
数据类型吗?
【讨论】:
以上是关于如何优化我的 mysql 请求以使用大型数据库的主要内容,如果未能解决你的问题,请参考以下文章