【问题标题】:MySQL query with 2 joins, large keylen leads to 'Copying to tmp table on disk' process hanging forever具有 2 个连接的 MySQL 查询,大 keylen 导致“复制到磁盘上的 tmp 表”进程永远挂起
【发布时间】:2013-12-03 01:32:11
【问题描述】:

我确定我一定是在做一些愚蠢的事情,但通常情况下我无法弄清楚它是什么。

我正在尝试运行此查询:

SELECT `f`.`FrenchWord`, `f`.`Pronunciation`, `e`.`EnglishWord`
FROM (`FrenchWords` f)
INNER JOIN `FrenchEnglishMappings` m ON `m`.`FrenchForeignKey`=`f`.`id`
INNER JOIN `EnglishWords` e ON `e`.`id`=`m`.`EnglishForeignKey`
WHERE `f`.`Pronunciation` =  '[whatever]';

当我运行它时,发生的事情对我来说似乎很奇怪。我得到了很好的查询结果,大约 0.002 秒内有 2 行。

但是,我的 CPU 也出现了巨大的峰值,SHOW PROCESSLIST 显示该查询的两个相同进程,状态为“复制到磁盘上的 tmp 表”。这些似乎一直在无休止地运行,直到我杀死它们或系统冻结。

所涉及的表都不大 - 每个表在 100k 到 600k 行之间。 tmp_table_sizemax_heap_table_size 都是 16777216。

编辑:EXPLAIN 上的声明给出:

+edit 将发音的 keylen 缩减为 112

+----+-------------+-------+--------+-------------------------------------------------------------+-----------------+---------+----------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys                                               | key             | key_len | ref                        | rows | Extra                                        |
+----+-------------+-------+--------+-------------------------------------------------------------+-----------------+---------+----------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | f     | ref    | PRIMARY,Pronunciation                                       | Pronunciation   | 112     | const                      |    2 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | m     | ref    | tmpindex,CombinedIndex,FrenchForeignKey,EnglishForeignKey   | tmpindex        | 4       | dict.f.id                  |    1 | Using index                                  |
|  1 | SIMPLE      | e     | eq_ref | PRIMARY,id                                                  | PRIMARY         | 4       | dict.m.EnglishForeignKey   |    1 |                                              |
+----+-------------+-------+--------+-------------------------------------------------------------+-----------------+---------+----------------------------+------+----------------------------------------------+

如果有人能指出可能导致这种情况的原因,我将不胜感激。 我真正不明白的是 MySQL 正在做什么 - 如果查询完成,那么它肯定不需要做任何其他事情?

更新

感谢所有回复。我从他们所有人身上学到了一些东西。在听从 nrathaus 的建议后,这个查询大大加快了。我向包含 unhex( md5 ( Pronunciation ) ) 的 FrenchWords 添加了一个 PronunciationHash binary(16) 列。使用 16 的 keylen 进行索引(而 Pronunciation 上的 varchar 索引为 600+),现在查询速度要快得多。

【问题讨论】:

  • 你的索引是什么?您是否尝试过在此语句中使用说明?
  • 谢谢。 FrenchWord、Pronunciation、EnglishWord、FrenchForeignKey、EnglishForeignKey 的索引。 FrenchWords 和 EnglishWords 上的 id 是主要的。编辑后的答案包括解释。
  • 在没有任何聚合函数的情况下,您对 GROUP BY 子句的使用显得不合适。
  • 我使用GROUP BY 因为FrenchWord 在该表中不是唯一的,在这种情况下,我只想查看每个FrenchWord 一次(其他时候他们需要根据其他列分开。我应该以不同的方式处理?

标签: mysql sql codeigniter join sql-order-by


【解决方案1】:

正如 EXPLAIN 所说,您的密钥大小是 HUGE : 602,这需要 MySQL 写下数据。

你需要(大大)减少keylen,我相信推荐低于128。

我建议您创建一个名为 MD5_FrenchWord 的列,其中将包含 FrenchWord 的 MD5 值。然后将此列用于 GROUP BY。这假设您正在寻找相似之处,当您分组而不是实际值时

【讨论】:

  • 谢谢。我试图尽可能减少keylen,但并没有缓解这个问题。首先,我将该列的排序规则从 utf8_unicode_ci 更改为 ascii_general_ci,因为该列仅包含 ascii 字符。这将 keylen 降低到 202。然后我将它从 VARCHAR 200 减少到 VARCHAR 110,因为它包含的最长字符串是 110。keylen 现在是 112。查询仍然会导致很长的“复制到磁盘上的 tmp 表”过程。
  • 不鼓励将 varchar 用作键,一般来说,text、char 等都不是好的键。好的键是唯一的元素,例如数字、GID 等。将 varchar 更改为 char(110) 是的,我知道它是更多数据,将使它们的键更有效。顺便说一句:你真的需要 GROUP BY 的数据吗?还是只是为了找到它们相同的地方?如果需要“相同”,请使用 MD5(FrenchWord)(或 SHA(...))作为 KEY 值,这将减少 keylen,同时不会失去 GROUP BY 的能力
  • 非常感谢。结果证明这是一个很好的解决方案。我现在正在二进制(16)列中搜索 md5 哈希,这要快得多。今天学到了一些非常有用的东西!
【解决方案2】:

您误用了GROUP BY。除非您的 SELECT 子句中还包含 MAX(something)COUNT(*) 之类的摘要函数,否则此子句完全没有意义。

尝试删除GROUP BY 看看是否有帮助。

不清楚你想用GROUP BY做什么。但是,如果您要尝试对结果集进行重复数据删除,则可以尝试 SELECT DISTINCT

【讨论】:

  • 谢谢。我已将查询更改为使用 SELECT DISTINCT 而不是 GROUP BY。我还取出了 ORDER BY,但仍然得到巨大的“复制到磁盘上的 tmp 表”过程。
【解决方案3】:

进一步看这个问题,您似乎可以从几个复合索引中受益。

首先,您能否确保您的表声明在尽可能多的列中包含NOT NULL

其次,您要从 Frenchwords 表中检索 Pronunciation、FrenchWord 和 id,因此请尝试在该表上使用此复合索引。然后,您的查询将能够直接从索引中获取所需的内容,从而节省大量磁盘 io。请注意,在复合索引声明中首先提到了发音,因为这是您要搜索的值。这允许 MySQL 对索引进行查找,并直接从索引中获取所需的其他信息,而无需返回表本身。

(Pronunciation, FrenchWord, id)

您正在从通过 id 查找的 Englishwords 中检索 Englishword。因此,同样的推理也适用于这个复合索引。

(id, Englishword)

最后,一旦你使用SELECT DISTINCT,我就无法判断你的 ORDER BY 是干什么用的。你可以试着摆脱它。但这可能没什么区别。

试试这个。如果您在进行这些更改后您的 MySQL 服务器仍然抖动,则您有某种配置问题。

【讨论】:

  • 再次感谢。这才是真正的教育!那么在经常一起检索的列上按检索顺序创建索引的一般原则是什么?
猜你喜欢
  • 1970-01-01
  • 2013-12-07
  • 1970-01-01
  • 2018-01-13
  • 1970-01-01
  • 2023-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多