【问题标题】:MySQL Long Query Progress MonitoringMySQL 长查询进度监控
【发布时间】:2011-03-28 20:53:16
【问题描述】:

作为我的问题的序言,我知道没有直接支持这样的事情。我正在寻找的是任何一种变通方法,或者是复杂的推导,可以让我得到一个值得尊敬的结果。

我正在使用集群引擎处理一个相当大的 MySQL 集群(表 > 4 亿行)。

是否有人知道直接检索或通过 mysql 中的长查询获得某种(或更好)准确的进度指示的方法?我有一些查询可能需要长达 45 分钟,我需要确定我们的处理完成率是 10% 还是 90%。

编辑:

根据 cmets 的要求,这里是 一个 导致我最初的问题的查询的提炼和通用版本...

SELECT `userId`
FROM    `openEndedResponses` AS `oe`
WHERE
    `oe`.`questionId` = 3 -- zip code
    AND (REPLACE( REPLACE( `oe`.`value`, ' ', '' ), '-', '' ) IN ( '30071', '30106', '30122', '30134', '30135', '30168', '30180', '30185', '30187', '30317', '30004' ));

此查询针对具有约 9500 万行的单个表运行。运行查询需要 8 秒,传输数据需要 13 秒(总共 21 秒)。考虑到表的大小,以及正在使用字符串操作函数的事实,我会说它运行得非常快。但是,对于用户来说,它仍然是 21 秒,显示为卡住或空闲。一些进展迹象将是理想的。

【问题讨论】:

  • 一个查询最多需要 45 分钟,还是很多小的 INSERT/UPDATE/DELETE 查询?
  • KOGI,如果您能够解决您的问题,您应该将其添加为答案。
  • 我无法解决我的问题。因此,每个人都 +1 :)
  • 你能把需要这么长时间的查询发给我们吗???也许可以更好地优化 IT...
  • 很遗憾我不能,因为它包含敏感的公司数据,但也许我可以提炼一点供公众查看......

标签: mysql sql


【解决方案1】:

我知道这是一个老问题,但我一直在寻找类似的答案,试图弄清楚我的更新需要多长时间才能处理 2.5 亿行的查询。

如果你运行:

SHOW ENGINE INNODB STATUS \G

然后在 TRANSACTIONS 下找到有问题的交易,检查此部分:

---TRANSACTION 34282360, ACTIVE 71195 sec starting index read
mysql tables in use 2, locked 2
1985355 lock struct(s), heap size 203333840, 255691088 row lock(s), undo log entries 21355084

重要的一点是“撤消日志条目”。对于每个更新的行,在我的情况下,它似乎添加了一个撤消日志条目(几秒钟后再次尝试运行它,看看添加了多少)。

如果您跳到状态报告的末尾,您会看到:

Number of rows inserted 606188224, updated 251615579, deleted 1667, read 54873415652
0.00 inserts/s, 1595.44 updates/s, 0.00 deletes/s, 3190.88 reads/s

在这里我们可以看到正在应用的速度更新是每秒 1595.44 行(尽管如果您同时运行其他更新查询,那么这个速度可能会在您的查询之间分开)。

因此,我知道 21m 已更新为 (250m-21m) 229m 行。

229,000,000 / 1600 = 143,125 秒 (143,125 / 60) / 60 = 39.76 小时

看来我可以再玩几天大拇指了。除非这个答案是错误的,否则我会在那之前的某个时候更新它!

【讨论】:

  • 啊,我想我只是在制造噪音,出于某种原因,我认为这是关于更新,而不是选择查询
  • 您的噪音对于估计我的 DELETE 语句的运行时间非常有用,非常感谢!似乎“行锁”也是一个很好的数字,我怀疑它告诉你到目前为止查询已经查看了多少行(所有涉及的表),而“撤消日志”是它改变了多少.
  • 我正在寻找这个,多么有用的噪音
  • \G 对我不起作用,它会产生语法错误。也许我使用的是旧版本的 mysql?
  • 对我来说,撤消日志计数和行锁计数会定期重置。我正在向一个巨大的表中添加一列。它说“复制到 tmp 表”。报告行操作的结束似乎是一个db累积运行总计。
【解决方案2】:

我能够通过查询要处理的行数然后将处理分成一个循环来估计类似的情况,一次只处理总行数的一个子集。

整个循环相当复杂,但基本逻辑如下:

SELECT @minID = Min(keyColumn) FROM table WHERE condition
SELECT @maxID = Max(keyColumn) FROM table WHERE condition
SELECT @potentialRows = (@maxID - @minID) / @iterations

WHILE @minID < @maxID
BEGIN
    SET @breakID = @minID + @potentialRows
    SELECT columns FROM table WITH (NOLOCK, ...)
    WHERE condition AND keyColumn BETWEEN @minID AND @breakID

    SET @minID = @breakID + 1
END

请注意,如果 ID 分布均匀,这种方法效果最好。

【讨论】:

  • 那么,如果我理解正确,您执行一次查询以获取 minId,再次获取 maxId,然后第三次(由多个分块子查询组成)?虽然这肯定会提供一些进度指示,但它实际上使总查询时间增加了三倍(如果将最小值和最大值的确定组合到单个查询中,至少会加倍)。我错过了什么?
  • @KOGI:如果您正在为其计算 MIN/MAX 值的列已编入索引,则您应该检查诸如 CEIL(LOG2(rows)) 总行之类的内容——花费的时间要少得多.如果该列没有被索引,它可能应该是如果你的 SELECT 是基于它的,并且你将不得不消耗停机时间来添加该索引,因为你的表很大......
  • 感谢显眼的编译器。我喜欢这个想法,到目前为止,这是我遇到的最佳选择。我想知道运行多个分块查询与一个大型查询的性能影响(忽略 MIN/MAX 的确定)...
  • 是的,keyColumn 应该被索引,在我的情况下,确定最小值和最大值的时间可以忽略不计。根据完整的查询和执行计划,它可能比单个查询花费更少或更多的时间来执行。只有分析才能确定它对您的效果。
  • +1 不能接受这些答案中的任何一个作为解决方案,但仍然感谢您的想法和努力!谢谢!
【解决方案3】:

我发现 here 由 Baron Schwartz 写的这个老问题有一个有希望的答案。这不是一个精确和完整的解决方案,但它确实提供了一些客观的估计材料,如果您只在服务器上运行该查询而不是其他任何东西

您在查询已运行时运行此命令

mysqladmin extended -r -i 10 | grep Handler
  • 10 是命令自身重复的秒数,因此请等待刷新
  • 如果您需要进行身份验证,请添加 -u root -p 之类的内容
  • 如果您确切知道要查找的处理程序,则可以使grep 更加集中,例如Handler_read_rnd_next 似乎对SELECT 有好处
  • 忽略第一个输出,使用第二个及以下输出
  • 使用Ctrl-C退出

现在得到那个数字并做你的数学运算。确定每秒处理的行数,并且根据您对表大小的了解,您可能能够获得相当精确的总时间估计值。

免费的额外提示:该命令似乎没有进入 Bash 历史记录(可能是因为使用 Ctrl-C 退出,您可以使用 history -s mysqladmin extended -r -i 10 -u root -p | grep Handler 手动添加它

【讨论】:

  • 这太棒了,应该是公认的答案。
【解决方案4】:

我不认为 mysql 支持 我确定 MySQL 不支持任何有关正在运行的查询进度的指示。唯一的解决方案是优化/拆分查询。 正如 Dour High Arch 建议的那样,选择可以按 id 拆分。这是来自 3300 万行表的查询:

mysql> SELECT SQL_NO_CACHE min(id), max(id) FROM `urls`;
+---------+----------+
| min(id) | max(id)  |
+---------+----------+
|    5000 | 35469678 |
+---------+----------+
1 row in set (0.00 sec)

您最好使用整数或至少日期字段进行拆分。它应该是 primaryunique 索引,并且不应允许空值。

【讨论】:

  • +1 不能接受这些答案中的任何一个作为解决方案,但仍然感谢您的想法和努力!谢谢!
【解决方案5】:

如果您尝试的是复杂查询,EXPLAIN SQL 命令或 MySQL 查询分析器可能有助于了解正在发生的事情。如果它只是一个大型查询,您可以尝试使用 SELECT INTO 和/或在 SELECT 查询中使用 LIMIT/OFFSET 子句创建一个临时表。如果在原始表上使用 LIMIT/OFFSET,则可能需要将事务级别设置为可序列化 IIRC,以便在迭代数据时获得一致的读取。如果您先创建一个临时表,则该表应始终保持一致。

【讨论】:

  • +1 不能接受这些答案中的任何一个作为解决方案,但仍然感谢您的想法和努力!谢谢!
【解决方案6】:

目前——对于我非常具体的情况——似乎没有真正的解决方案。由于我无法将我的查询拆分为几个较小的查询,并且它首先证明对select count(*) 适得其反,然后运行“真实”查询(将已经非常缓慢的查询的执行时间加倍),因此任何解决方法似乎都不可行。也许很快,MySQL 会支持这样的东西

【讨论】:

  • 你为什么select count(*)
  • 这已经过时了,但COUNT(*) 是一种确定有多少行以便将查询分块为多个较小查询的方法。
【解决方案7】:

如果您的查询涉及对大表的线性扫描,您通常可以通过在包含该表的文件上运行 pmonitor 来获得出色的估计。包括--update 选项,因为MySQL 以更新模式打开表文件。

示例:

$ sudo pmonitor --update --file=/home/mysql/ghtorrent/commits.MYD --interval=5 /home/mysql/ghtorrent/commits.MYD 31.66% /home/mysql/ghtorrent/commits.MYD 33.16% ETA 0:03:42 /home/mysql/ghtorrent/commits.MYD 34.85% ETA 0:03:24 /home/mysql/ghtorrent/commits.MYD 36.43% ETA 0:03:32 /home/mysql/ghtorrent/commits.MYD 38.36% ETA 0:03:12 /home/mysql/ghtorrent/commits.MYD 40.21% ETA 0:03:01 /home/mysql/ghtorrent/commits.MYD 41.95% ETA 0:02:54 [...] /home/mysql/ghtorrent/commits.MYD 92.01% ETA 0:00:24 /home/mysql/ghtorrent/commits.MYD 93.85% ETA 0:00:18 /home/mysql/ghtorrent/commits.MYD 95.76% ETA 0:00:12 /home/mysql/ghtorrent/commits.MYD 97.60% ETA 0:00:07 /home/mysql/ghtorrent/commits.MYD 98.83% ETA 0:00:03 /home/mysql/ghtorrent/commits.MYD 100% ETA 0:00:00

如果您不知道要监控的文件,请使用 --diff 选项运行 pmonitor。这将向您显示取得进展的文件。

示例

$ sudo pmonitor --update -diff --command=mysqld -i 60 [...] /home/mysql/ghtorrent/projects.MYD 22.41% ETA 2:01:41 /home/mysql/ghtorrent/projects.MYD 23.13% ETA 1:53:23 /home/mysql/ghtorrent/projects.MYD 23.84% ETA 1:50:27

【讨论】:

    【解决方案8】:

    您需要执行以下操作来改进以下查询:

    SELECT `userId`
    FROM    `openEndedResponses` AS `oe`
    WHERE
        `oe`.`questionId` = 3 -- zip code
        AND (REPLACE( REPLACE( `oe`.`value`, ' ', '' ), '-', '' ) IN ( '30071', '30106', '30122', '30134', '30135', '30168', '30180', '30185', '30187', '30317', '30004' ));
    

    您需要确保 oe.questionId 已编入索引; 当 oe.questionId 为 3 时,您需要确保 oe.value 在整个表中没有任何空间;假设 4 或 5 可以是城市名称,您仍然希望在其中允许空格。

    通过这样做,您将能够删除所有 REPLACE,这将使 MySQL 使用 oe.value 中的索引。

    然后,MySQL 将合并两个索引,并在处理方面更快地为您提供结果。

    如果你有很多重复的userId;你会想要对它们进行分组;这样索引中的条目就会立即被丢弃。您仍然需要扫描整个合并索引;但是结果集的大小将花费更少的时间来传输;不到 13 秒!

    试一试,让我们随时了解结果

    最好的!

    【讨论】:

    • 感谢您的建议。这个问题现在真的很老了,但这绝对有可能加速查询本身。然而,不管速度如何,我一直在寻找一种监控进度的方法。有些查询并不快,而且无法进一步优化——我需要一种方法来查看查询执行的进度。
    【解决方案9】:

    如何对 mysql 表进行分区,以便分散读/写负载。看看尝试将每个分区限制为 5000 万行(显然取决于您的硬件)

    【讨论】:

    • 感谢您的建议。这绝对有可能加快查询本身的速度,但不管速度如何,我一直在寻找一种监控进度的方法。
    • 啊!现在我重新阅读您的问题我会考虑一下。这是上班路上的快速反应:)
    • 在我的脑海中,我在想,如果您将表拆分为多个分区,即使可能,将它们分成单独的表,您可以使您的查询执行得更快,您不必担心监控...如果您向我们提供您需要执行的列和读/写功能,我们(如此)可以研究如何最好地对表进行分区、索引,以更快地执行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-14
    • 1970-01-01
    • 1970-01-01
    • 2012-08-15
    相关资源
    最近更新 更多