【问题标题】:mysql not using index?mysql不使用索引?
【发布时间】:2017-10-15 17:16:51
【问题描述】:

我有一个表,其中包含 word、A_、E_、U_ 等列。这些带有 X_ 的列是小整数,其值是特定字母在单词中存在的次数(以帮助稍后优化通配符搜索查询)。

总共有 252k 行。如果我像 WHERE u_ > 0 那样进行搜索,我会得到 60k 行。但是,如果我对该选择进行解释,它会说有 225k 行要通过并且没有索引是可能的。为什么?列被添加为索引。为什么它没有说有 60k 行要通过并且可能的键是 U_?

列出表上的索引(也奇怪,其他人都分组在A_索引下)

相比之下,如果我运行查询: where id > 250000 我得到 2983 个结果,如果我解释了该选择,它会说有 2982 行和要使用的主键。

顺便说一句,如果我按 U_ 分组,我会得到:(但可能无关紧要,因为我已经说过查询返回 60k 结果)

编辑:

如果我创建列 U (varchar(1)) 并更新 U = 'U' where U_ > 0,那么如果我选择 WHERE U = 'U' 我也会得到 60k 行(显然),但是如果我解释一下,我就会明白:

仍然不太好(120k 行不是 60k 行),但至少比前一种情况下的 225k 行好。虽然这个解决方案比第一个解决方案更小猪,但可能更有效。

【问题讨论】:

    标签: mysql indexing


    【解决方案1】:

    我的经验是 MySQL 会选择执行表扫描,即使您正在搜索的列上有索引,如果您的查询会选择表中大约 25% 以上的行。

    原因是在 InnoDB 中使用二级索引比使用主索引要多一些工作。

    1. 在二级索引中查找值,例如您在 u_ 上的索引。
    2. 读取索引条目,并找到存储u_中该值的行的对应主键值。
    3. 按主键查找行。

    实际上至少 double 是通过辅助键查找的工作。如果您最终匹配表的一小部分行,这不是问题,并且肯定存在二级索引对您的查询非常重要的情况。所以不要勉强使用二级索引。

    但是,如果您的查询匹配的行太多,并且这成为表的很大一部分,那么仅从头到尾扫描表的工作量就会减少。

    以此类推,为什么书后的索引中没有“the”这个词?因为条目自然会列出书中的每一页,如果你参考索引然后用它来引导你到书的主要部分的每一页,那将是一种浪费.读这本书会更好。

    MySQL 没有任何官方记录的阈值来选择表扫描而不是索引搜索。 25% 的数字只是我的经验(实际上有时它似乎更接近 21%,但我对代码的了解不够深入,无法准确理解阈值是如何计算的)。

    我已经看到匹配行的比例非常接近实现中的任何阈值的情况,并且优化器的行为实际上可以从一个查询翻转到下一个查询,从而导致性能高度可变。

    如果这种情况适用于您,您可以使用index hint 让 MySQL 的优化器假装 tablescan 过于昂贵,并且它应该更喜欢索引而不是 tablescan。这是通过FORCE INDEX 提示完成的。

    SELECT * FROM words FORCE INDEX(U_) WHERE U_ > 0
    

    我仍然尝试保守地使用索引提示。除非在极少数情况下,它们不是必需的,并且使用索引提示意味着您的查询必须包含索引名称。这使得在不破坏应用程序代码的情况下很难更改索引。

    【讨论】:

    • 你好比尔。 “the”这个词的好例子。也谢谢你的回复。使用强制索引(u_),它确实使用该索引并且行数是 120k,所以对我来说,它似乎比通过 250k 更好,但是一旦我结合多个条件(列),强制可能无法使用,那么让优化器可能会更好决定做什么(如你所说),但感谢您提供的信息。
    • MySQL 8.0(和 MariaDB 10.0)具有“直方图”,可以通过不平衡的索引“解决”这个问题。
    • @RickJames 这不是由不平衡索引引起的问题。即使索引是唯一的,也可以运行匹配 > 25% 索引的查询。
    【解决方案2】:

    您询问的是后端查询优化器。特别是您要问:“它如何选择访问路径?为什么在这里索引但在那里进行表扫描?”

    让我们考虑一下那个优化器。它在优化什么?时光荏苒,满怀期待。它有一个模型来说明顺序读取和随机读取需要多长时间,以及查询选择性,即查询返回的预期行数。它从多个替代访问路径中选择似乎需要最少经过时间的路径。

    您的id > 250000 查询有一些好处:

    1. 选择性好,因此不到 1% 的行会出现在结果集中
    2. id 是主键,因此所有列在导航到 btree 中的正确位置后立即可用

    这导致优化器计算的索引访问路径的预期经过时间远小于表扫描的预期时间。

    另一方面,您的u_ > 0 查询的选择性很差,将将近四分之一的行拖到结果集中。此外,该索引不是您 *all 列值复制到结果集中的需求的覆盖索引。所以优化器预测它必须读取四分之一的索引块,然后基本上是它们指向的所有数据行块。因此,与 tablescan 相比,我们必须从磁盘读取 更多 个块,并且它们将是随机读取而不是顺序读取。两者都反对使用索引,因此选择了 tablescan,因为它最便宜。此外,请记住,通常多个行将适合单个磁盘块或单个读取请求。如果它始终选择索引访问路径,我们将其称为悲观器,即使在索引磁盘 I/O 需要更长时间的情况下也是如此。

    总结建议

    当您的查询具有良好的选择性时,在单个列上使用索引,返回远少于关系行的 1%。当您的查询选择性较差并且您愿意在空间与时间之间进行权衡时,请使用 covering index

    【讨论】:

    • 你好。如果我在列上添加索引,我希望它会大大加快“访问路径”,并且它还将在描述查询中声明将使用索引并且扫描行的总数会更小。即在这种情况下,我希望 mysql 将通过 U_ 的值预先创建行组,实际上不需要使用其他值扫描其他行。但是解释查询说的是别的。它说它将使用 NO 索引,并且要扫描的行数基本上是所有行。那么索引的意义何在。
    • “通过 u_ 的值预先创建的行组”的名称为“覆盖索引”。您的 EXPLAIN 输出告诉您 u_ 已编入索引,但它不是覆盖索引,因此它不提供最便宜的访问路径。如果您只要求select u_, e_ from ...,在 (u_, e_) 上创建复合索引后,可能会说服优化器选择不同的访问路径。顺便说一句,请注意,只有少数列的窄切片将适合更少的磁盘块,因此即使是盲查询(无选择性)也可能更喜欢覆盖索引而不是基本行的表扫描。
    • 你是对的,如果我执行 EXPLAIN SELECT u_ FROM words where u_ > 0 它使用 U_ 作为索引并且行数为 120k(与强制索引一样)。很高兴知道。
    猜你喜欢
    • 2018-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-27
    • 2013-04-07
    • 2013-07-10
    • 2012-12-31
    • 2013-12-07
    相关资源
    最近更新 更多