如何优化 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 小时的主要内容,如果未能解决你的问题,请参考以下文章

在 Sequel Pro 中禁用严格模式

mysql5.7.X版本only_full_group_by问题解决

GROUP BY不适用于MySQL 5.7,因为5.7使用SQL_MODE的“ONLY_FULL_GROUP_BY”选项。

如何优化Mysql千万级快速分页

如何在 MYSQL 中优化此查询?需要做啥

mysql sql优化之 优化GROUP BY 和 DISTINCT