如何优化 mysql 查询,因为 Full ProcessList 显示发送数据超过 24 小时
Posted
技术标签:
【中文标题】如何优化 mysql 查询,因为 Full ProcessList 显示发送数据超过 24 小时【英文标题】:How to optimise mysql query as Full ProcessList is showing Sending Data for over 24 hours 【发布时间】:2020-02-27 17:24:01 【问题描述】:我有以下永远运行的查询,我正在寻找是否有任何可以优化它的方法。这是在一个总共有 1,406,480 行数据的表上运行的,但除了 Filename 和 Refcolumn 之外,ID 和 End_Date 都已被索引。
我的查询:
INSERT INTO UniqueIDs
(
SELECT
T1.ID
FROM
master_table T1
LEFT JOIN
master_table T2
ON
(
T1.Ref_No = T2.Ref_No
AND
T1.End_Date = T2.End_Date
AND
T1.Filename = T2.Filename
AND
T1.ID > T2.ID
)
WHERE T2.ID IS NULL
AND
LENGTH(T1.Ref_No) BETWEEN 5 AND 10
)
;
解释结果:
不索引 Ref_No 的原因是这是一个文本列,因此当我尝试为该列建立索引时出现 BLOB/TEXT 错误。
如果有人能建议我如何加快这个查询,我将不胜感激。
谢谢
感谢 Bill 在多列索引方面我已经取得了一些进展。我首先运行了这段代码:
CREATE INDEX I_DELETE_DUPS ON master_table(id, End_Date);
然后我添加了一个新列来显示 Ref_No 的长度,但由于我的 mysql 版本是 5.5,因此必须从 Bill 提到的查询中更改它。所以我分 3 步运行它:
ALTER TABLE master_table
ADD COLUMN Ref_No_length SMALLINT UNSIGNED;
UPDATE master_table SET Ref_No_length = LENGTH(Ref_No);
ALTER TABLE master_table ADD INDEX (Ref_No_length);
最后一步是使用 where 子句更改插入查询的长度。改为:
AND t1.Ref_No_length between 5 and 10;
然后我运行了这个查询,并在 15 分钟内将价值 280k 的 id 插入到我的 UniqueIDs 表中。我确实去更改了我的插入脚本,看看是否可以通过执行以下操作为长度添加更多值:
AND t1.Ref_No_length IN (5,6,7,8,9,10,13);
这是为了引入长度也等于 13 的值。这个查询花费了更长的时间,准确地说是 2 小时 50 分钟,但是查找所有长度为 13 的行的额外要求给了我额外的 700k唯一 ID。
我正在寻找使用 IN 子句优化查询的方法,但在此查询保持运行 24 小时的情况下,这是一个很大的改进。非常感谢比尔。
【问题讨论】:
长度函数很不幸,因为它禁止使用索引。如果 ref no 是整数,则存在明显的改进 (“发送数据”是一个无用的非信息。) Michael,并且 t1.Ref_No_length 在 5 到 13 之间;而不是使用跳过 11 和 12 的 IN 列表可能会更快完成。 【参考方案1】:对于 JOIN,您应该在 (Ref_No, End_Date, Filename)
上有一个多列索引。
您可以像这样在 TEXT 列上创建prefix index:
ALTER TABLE master_table ADD INDEX (Ref_No(10));
但这不会帮助您根据 LENGTH() 进行搜索。索引仅有助于按索引值进行搜索,而不是按列上的函数。
在 MySQL 5.7 或更高版本中,您可以像这样创建一个虚拟列,并在为虚拟列计算的值上建立索引:
ALTER TABLE master_table
ADD COLUMN Ref_No_length SMALLINT UNSIGNED AS (LENGTH(Ref_No)),
ADD INDEX (Ref_No_length);
然后 MySQL 会识别出您在查询中的条件与虚拟列的表达式相同,并且会自动使用索引(例外:根据我的经验,这不适用于使用 JSON 函数的表达式)。
但这并不能保证索引会有所帮助。如果大多数行匹配长度在 5 到 10 之间的条件,优化器将不会打扰索引。使用索引可能比进行表扫描更费力。
【讨论】:
感谢比尔的回复。将仔细阅读您的建议并发布我的进展情况。 Bill,正在创建您提到的虚拟列,但不断收到错误消息。想想我的 MySQL 版本是 5.5.27,你提到它只适用于 5.7 及更高版本。 我还添加了一个没有 Ref_No 的多列索引,因为该索引再次出现 BLOB/TEXT 错误。使用第一个 Alter Table 代码为 Ref_No 添加了索引,但是当我查看解释查询到 1471073 时,这增加了行数。 EXPLAIN 中的rows
输出只是一个估计值。它可以高于或低于它实际读取的实际行数,但通常在一个数量级内是正确的。
您真的需要为Ref_No
列使用TEXT 吗?我在做一个假设,但我认为该名称的列中会包含很短的字符串,甚至可能是一个数字。【参考方案2】:
ID 和 End_Date 均已编入索引。
您有PRIMARY KEY(id)
和冗余 INDEX(id)
? PK 是唯一的密钥。
“都已编入索引”——INDEX(a), INDEX(b)
与 INDEX(a,b)
不同——它们有不同的用途。阅读“复合”索引。
这个查询听起来很像以非常慢的方式完成的“分组方式”最大值。 (唉,这可能来自在线文档。)
我在这里编译了完成该任务的最快方法:http://mysql.rjweb.org/doc.php/groupwise_max(有多个版本,基于 MySQL 版本以及您的代码可以/不能容忍的问题。)
请提供SHOW CREATE TABLE
。一个重要的问题:id
是主键吗?
这个复合索引可能有用:
(Filename, End_Date, Ref_No, -- first, in any order
ID) -- last
正如其他人所指出的,任何索引都不太可能对此有所帮助,因此 T1 需要进行全表扫描:
AND LENGTH(T1.Ref_No) BETWEEN 5 AND 10
如果 Ref_No
不能大于 191 个字符,请将其更改为 VARCHAR
以便可以在索引中使用。哦,我问过SHOW CREATE TABLE
吗?如果你做不到VARCHAR
,那我推荐的复合索引是
INDEX(Filename, End_Date, ID)
【讨论】:
瑞克谢谢你看这个。将仔细阅读您所写的内容,看看我可以做出哪些进一步的改进。以上是关于如何优化 mysql 查询,因为 Full ProcessList 显示发送数据超过 24 小时的主要内容,如果未能解决你的问题,请参考以下文章
mysql5.7.X版本only_full_group_by问题解决
GROUP BY不适用于MySQL 5.7,因为5.7使用SQL_MODE的“ONLY_FULL_GROUP_BY”选项。