为什么MySQL(InnoDB)的性能变化很大?

为什么MySQL(InnoDB)的性能变化很大?,mysql,count,innodb,Mysql,Count,Innodb,我开始调查为什么Django管理员中的一些搜索速度非常慢。进一步挖掘,我发现MySQL 5.1、InnoDB表的性能在不同的查询中有很大的差异。例如: 此查询在Django生成的4个字段(2个相关字段)中查找“c”、“d”和“e”,耗时89毫秒,返回3093行: SELECT DISTINCT `donnees_artiste`.`id` FROM `donnees_artiste` LEFT OUTER JOIN `donnees_artiste_evenements` ON

我开始调查为什么Django管理员中的一些搜索速度非常慢。进一步挖掘,我发现MySQL 5.1、InnoDB表的性能在不同的查询中有很大的差异。例如:

此查询在Django生成的4个字段(2个相关字段)中查找“c”、“d”和“e”,耗时89毫秒,返回3093行:

SELECT DISTINCT `donnees_artiste`.`id`
    FROM `donnees_artiste`
LEFT OUTER JOIN `donnees_artiste_evenements`
    ON (`donnees_artiste`.`id` = `donnees_artiste_evenements`.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement`
    ON (`donnees_artiste_evenements`.`evenement_id` = `donnees_evenement`.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T4
    ON (`donnees_artiste`.`id` = T4.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T5
    ON (T4.`evenement_id` = T5.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T6
    ON (`donnees_artiste`.`id` = T6.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T7
    ON (T6.`evenement_id` = T7.`id`)

WHERE (
    (`donnees_artiste`.`nom` LIKE '%c%'
  OR `donnees_artiste`.`prenom` LIKE '%c%'
  OR `donnees_evenement`.`cote` LIKE '%c%'
  OR `donnees_evenement`.`titre` LIKE '%c%' )
AND (`donnees_artiste`.`nom` LIKE '%d%'
  OR `donnees_artiste`.`prenom` LIKE '%d%'
  OR T5.`cote` LIKE '%d%'
  OR T5.`titre` LIKE '%d%' )
AND (`donnees_artiste`.`nom` LIKE '%e%'
  OR `donnees_artiste`.`prenom` LIKE '%e%'
  OR T7.`cote` LIKE '%e%'
  OR T7.`titre` LIKE '%e%' )
);
如果我将“e”替换为“k”,那么它基本上是相同的查询,需要8720 ms 100x的增加,并返回931行

SELECT DISTINCT `donnees_artiste`.`id`
    FROM `donnees_artiste`
LEFT OUTER JOIN `donnees_artiste_evenements`
    ON (`donnees_artiste`.`id` = `donnees_artiste_evenements`.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement`
    ON (`donnees_artiste_evenements`.`evenement_id` = `donnees_evenement`.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T4
    ON (`donnees_artiste`.`id` = T4.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T5
    ON (T4.`evenement_id` = T5.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T6
    ON (`donnees_artiste`.`id` = T6.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T7
    ON (T6.`evenement_id` = T7.`id`)

WHERE (
    (`donnees_artiste`.`nom` LIKE '%c%'
  OR `donnees_artiste`.`prenom` LIKE '%c%'
  OR `donnees_evenement`.`cote` LIKE '%c%'
  OR `donnees_evenement`.`titre` LIKE '%c%' )
AND (`donnees_artiste`.`nom` LIKE '%d%'
  OR `donnees_artiste`.`prenom` LIKE '%d%'
  OR T5.`cote` LIKE '%d%'
  OR T5.`titre` LIKE '%d%' )
AND (`donnees_artiste`.`nom` LIKE '%k%'
  OR `donnees_artiste`.`prenom` LIKE '%k%'
  OR T7.`cote` LIKE '%k%'
  OR T7.`titre` LIKE '%k%' )
);
这两个查询给出了相同的解释,因此没有任何线索

ID  SELECT_TYPE     TABLE   TYPE    POSSIBLE_KEYS   KEY     KEY_LEN     REF     ROWS    EXTRA
1   SIMPLE  donnees_artiste     ALL     None    None    None    None    4368    Using temporary; Using filesort
1   SIMPLE  donnees_artiste_evenements  ref     artiste_id,donnees_artiste_evenements_eb99df11  artiste_id  4   mmac.donnees_artiste.id     1   Using index; Distinct
1   SIMPLE  donnees_evenement   eq_ref  PRIMARY,donnees_evenements_id_index     PRIMARY     4   mmac.donnees_artiste_evenements.evenement_id    1   Using where; Distinct
1   SIMPLE  T4  ref     artiste_id,donnees_artiste_evenements_eb99df11  artiste_id  4   mmac.donnees_artiste.id     1   Using index; Distinct
1   SIMPLE  T5  eq_ref  PRIMARY,donnees_evenements_id_index     PRIMARY     4   mmac.T4.evenement_id    1   Using where; Distinct
1   SIMPLE  T6  ref     artiste_id,donnees_artiste_evenements_eb99df11  artiste_id  4   mmac.donnees_artiste.id     1   Using index; Distinct
1   SIMPLE  T7  eq_ref  PRIMARY,donnees_evenements_id_index     PRIMARY     4   mmac.T6.evenement_id    1   Using where; Distinct
另外,如果我对第一个查询进行计数,则需要11200毫秒

SELECT COUNT(DISTINCT `donnees_artiste`.`id`)
    FROM `donnees_artiste`
LEFT OUTER JOIN `donnees_artiste_evenements`
    ON (`donnees_artiste`.`id` = `donnees_artiste_evenements`.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement`
    ON (`donnees_artiste_evenements`.`evenement_id` = `donnees_evenement`.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T4
    ON (`donnees_artiste`.`id` = T4.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T5
    ON (T4.`evenement_id` = T5.`id`)
LEFT OUTER JOIN `donnees_artiste_evenements` T6
    ON (`donnees_artiste`.`id` = T6.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement` T7
    ON (T6.`evenement_id` = T7.`id`)

WHERE (
    (`donnees_artiste`.`nom` LIKE '%c%'
  OR `donnees_artiste`.`prenom` LIKE '%c%'
  OR `donnees_evenement`.`cote` LIKE '%c%'
  OR `donnees_evenement`.`titre` LIKE '%c%' )
AND (`donnees_artiste`.`nom` LIKE '%d%'
  OR `donnees_artiste`.`prenom` LIKE '%d%'
  OR T5.`cote` LIKE '%d%'
  OR T5.`titre` LIKE '%d%' )
AND (`donnees_artiste`.`nom` LIKE '%e%'
  OR `donnees_artiste`.`prenom` LIKE '%e%'
  OR T7.`cote` LIKE '%e%'
  OR T7.`titre` LIKE '%e%' )
);
我的innodb_缓冲区_池_大小设置为高。我在所有相关字段和主键上都有索引,并且我已经优化了我的表

那么,为什么第一个查询如此之快而另外两个查询如此之慢呢?这3个查询只是示例。很多时候,我只是从查询中更改或删除一个字符,这对查询时间有很大影响。但我看不到任何模式

更新

性能问题肯定来自Django如何生成这些查询。所有这些冗余的左外连接链接在一起会降低性能。目前,我还不完全清楚这是Django SQL生成器中的一个bug,是如何为搜索字段构建查询的bug,还是Django开发人员所期望的那样工作。我还在调查,但在Django的行为中至少有一件奇怪的事情

如果我运行的这个查询不一定等同于第二个查询,但距离不远,结果非常快161毫秒,没有缓存:

SELECT DISTINCT `donnees_artiste`.`id`
    FROM `donnees_artiste`
LEFT OUTER JOIN `donnees_artiste_evenements`
    ON (`donnees_artiste`.`id` = `donnees_artiste_evenements`.`artiste_id`)
LEFT OUTER JOIN `donnees_evenement`
    ON (`donnees_artiste_evenements`.`evenement_id` = `donnees_evenement`.`id`)

WHERE (
    (`donnees_artiste`.`nom` LIKE '%c%'
  OR `donnees_artiste`.`prenom` LIKE '%c%'
  OR `donnees_evenement`.`cote` LIKE '%c%'
  OR `donnees_evenement`.`titre` LIKE '%c%' )
AND (`donnees_artiste`.`nom` LIKE '%d%'
  OR `donnees_artiste`.`prenom` LIKE '%d%'
  OR `donnees_evenement`.`cote` LIKE '%d%'
  OR `donnees_evenement`.`titre` LIKE '%d%' )
AND (`donnees_artiste`.`nom` LIKE '%k%'
  OR `donnees_artiste`.`prenom` LIKE '%k%'
  OR `donnees_evenement`.`cote` LIKE '%k%'
  OR `donnees_evenement`.`titre` LIKE '%k%' )
);
第二次更新


最后,这在Django中不是一个bug,我很确定这是期望的行为。其思想是,在多术语搜索中,下一个术语的搜索是在前一个术语返回的子集上完成的,因此,对于相关字段,所有术语都不必在同一行中才能进行匹配。为此,DB必须使用每个子集创建临时表并扫描它。这就解释了为什么会有很多变化,因为如果第一个术语只匹配几行,那么临时表将很小,而后续术语的搜索将很快,因为它们将在一个小表上完成。两个查询之间的差异很小,但Django查询通常可以返回更多匹配项。

类型的条件:

WHERE column LIKE '%c%'
无法对列使用索引。因此,必须对这些列进行完全扫描

您有多个这样的条件,您正在使用或在它们之间使用,以确保所有这些表都将被扫描。最后,您应该注意:Django正在添加DISTINCT,这可能需要在返回结果之前进行最终的文件排序

我找不到一个解释来解释性能上的巨大差异。也许第一个查询是缓存的。您能否尝试在查询结束时添加ORDER NY NULL并计时


生成的查询设计得也不是很好,因为它可能以一个小型笛卡尔连接结束。将一个基表连接到多个与基表具有1对多关系的表。这就是性能不佳的原因,查询计划将有助于澄清这一点

该类型的条件:

WHERE column LIKE '%c%'
无法对列使用索引。因此,必须对这些列进行完全扫描

您有多个这样的条件,您正在使用或在它们之间使用,以确保所有这些表都将被扫描。最后,您应该注意:Django正在添加DISTINCT,这可能需要在返回结果之前进行最终的文件排序

我找不到一个解释来解释性能上的巨大差异。也许第一个查询是缓存的。您能否尝试在查询结束时添加ORDER NY NULL并计时


生成的查询设计得也不是很好,因为它可能以一个小型笛卡尔连接结束。将一个基表连接到多个与基表具有1对多关系的表。这就是性能不佳的原因,查询计划将有助于澄清这一点

我认为,答案是在大多数情况下,e位于扫描字符串的开头和第一个搜索字符串中,允许缩短OR条件,而k的匹配发生在最后一个条件和字符串末尾的某个地方。由于带有k的行明显减少,因此应在没有任何匹配项的情况下对更多字符串进行完全扫描。

我认为,答案是,在大多数情况下,e位于扫描字符串的开头和第一个搜索字符串中,允许缩短或条件,而k的匹配发生在最后一个条件中,并且在字符串末尾的某个地方。而且,由于带有k的行明显减少,因此应在没有任何匹配项的情况下对更多字符串进行完全扫描。

如果使用带前导通配符的LIKE模式,则查询将不会受益于索引。以这种方式使用LIKE可能会非常低效,并且其执行时间可能会有很大差异 ot。 为什么?

LIKE语句后面的算法停止搜索一行,以防它遇到匹配项。 在这个使用无索引的场景中,MySQL应用了其他一些boosting算法,这些算法在某些情况下可能适用,也可能不适用。 为什么在第三个查询中使用COUNT会降低查询速度

我看到您正在使用innoDB

innoDB不会像MyISAM那样从存储/缓存的值中读取行数(如果列不为NULL),因为innoDB在“写”方面比在“读”方面更优化,而不是在MyISAM方面。使用innoDB表上的计数,每次执行完全表扫描或完全索引扫描

您的查询不使用任何索引,这可能是最坏的情况,因此会发生全表扫描,是的,它听起来很慢


我想您可能会感兴趣:

如果您使用带前导通配符的LIKE模式,您的查询将不会受益于索引。以这种方式使用LIKE可能会非常低效,并且其执行时间可能会有很大差异。 为什么?

LIKE语句后面的算法停止搜索一行,以防它遇到匹配项。 在这个使用无索引的场景中,MySQL应用了其他一些boosting算法,这些算法在某些情况下可能适用,也可能不适用。 为什么在第三个查询中使用COUNT会降低查询速度

我看到您正在使用innoDB

innoDB不会像MyISAM那样从存储/缓存的值中读取行数(如果列不为NULL),因为innoDB在“写”方面比在“读”方面更优化,而不是在MyISAM方面。使用innoDB表上的计数,每次执行完全表扫描或完全索引扫描

您的查询不使用任何索引,这可能是最坏的情况,因此会发生全表扫描,是的,它听起来很慢


我想你可能会感兴趣:

在“e”上快,在“k”上慢-如果字母出现在单词的前面,它应该会快一些,我想它只会根据需要扫描到单词的最深处。那些类似中间字符的命令很少有效率。对于出现在单词前面的字母,您的命令是正确的。见我对newtover的评论。对于那些类似中间字符的命令,如果您想搜索文本中的任意字符串,我认为没有其他选择?InnoDB确实不适合读取这样的文本字段。如果我非常需要这样做,我通常会将数据隐藏到MyISAM表上,并使用。虽然这对单个字母来说不好,因为它通常只停留在4个字母的单词上。服务器范围的设置,以更改它并使其效率低下。如果您事先知道需要扫描哪些字母,请提前完成工作负载,在插入时,为需要检查的字母使用标志,以确定其是否为小集合。如果只是字母表中的字母,可以使用长整数和设置位,然后使用按位and和要查找的字符。仍然需要对每一行进行操作,但要比字符串搜索快得多。在“e”上快,在“k”上慢——如果字母出现在单词的前面,应该会快得多,我想它只会根据需要扫描到单词的最深处。那些类似中间字符的命令很少有效率。对于出现在单词前面的字母,您的命令是正确的。见我对newtover的评论。对于那些类似中间字符的命令,如果您想搜索文本中的任意字符串,我认为没有其他选择?InnoDB确实不适合读取这样的文本字段。如果我非常需要这样做,我通常会将数据隐藏到MyISAM表上,并使用。虽然这对单个字母来说不好,因为它通常只停留在4个字母的单词上。服务器范围的设置,以更改它并使其效率低下。如果您事先知道需要扫描哪些字母,请提前完成工作负载,在插入时,为需要检查的字母使用标志,以确定其是否为小集合。如果只是字母表中的字母,可以使用长整数和设置位,然后使用按位and和要查找的字符。仍然需要对每一行进行操作,但比字符串搜索快得多。你说得对。我的4个字段中有一个几乎以“E”开头。所以,基本上这意味着MySQL需要搜索多远,需要的时间最长。所以,如果查询没有返回结果,这个查询肯定会很长,因为它必须扫描所有内容。你说得对。我的4个字段中有一个几乎以“E”开头。所以,基本上这意味着MySQL需要搜索多远,需要的时间最长。因此,如果查询没有返回结果,那么这个查询肯定会很长,因为它必须扫描所有内容。请参阅newtover答案以了解巨大差异的解释。我理解并同意生成的查询不是很好的设计,但您所说的“迷你笛卡尔连接”是什么意思?通过“查询计划”?笛卡尔连接意味着,如果一个艺术家平均有10个donnees\u evenement行,那么不带WHERE条件的查询将返回10x10xNumberOfArtists=1000xNumberOfArtists行。查询计划器实际上可能会生成这样一个计划,该计划生成一个包含如此多行和行的临时表
然后检查WHERE条件复杂的OR-AND条件和DINSTINCT可能不允许它生成更好的计划。我添加了前两个查询的解释。有关巨大差异的解释,请参见newtover答案。我理解并同意生成的查询不是很好的设计,但您所说的“迷你笛卡尔连接”是什么意思?通过“查询计划”?笛卡尔连接意味着,如果一个艺术家平均有10个donnees\u evenement行,那么不带WHERE条件的查询将返回10x10xNumberOfArtists=1000xNumberOfArtists行。查询计划器实际上可能会生成这样一个计划,生成一个包含这么多行的临时表,然后检查WHERE条件复杂的OR-and条件和DINSTINCT可能不允许它生成更好的计划。我添加了对前两个查询的解释。我严重怀疑使用MyISAM时这些查询是否会更快。我已经不推荐MyISAM迁移,我认为innoDB适合Etienne的需要,但我知道MyISAM缓存计数值:是的,可能会照你说的做。但只有当你想要整张桌子的数量时。不适用于具有复杂连接和Where条件的COUNT DISTINCE someColumn。我严重怀疑使用MyISAM进行任何查询是否会更快。我没有推荐MyISAM迁移,我认为innoDB适合Etienne的需要,但我知道MyISAM缓存计数值是的,可能会照你说的做。但只有当你想要整张桌子的数量时。不适用于具有复杂联接和Where条件的计数不同的someColumn。