由于特定部分导致的 MySQL 索引问题

Posted

技术标签:

【中文标题】由于特定部分导致的 MySQL 索引问题【英文标题】:Issues with a MySQL Index due to a specific part 【发布时间】:2017-10-24 12:44:53 【问题描述】:

查询是

SELECT row 
  FROM `table`
   USE INDEX(`indexName`)
 WHERE row1 = '0'
   AND row2 = '0' 
   AND row3 >= row4 
   AND (row5 = '0' OR row5 LIKE 'value')

我有以下 mysql 查询,我创建了一个索引供使用;

CREATE INDEX indexName ON `table` (row1, row2, row3, row5);

但是,性能并不是很好。它在 6-12 秒内从 5.9+ 百万行表中提取了大约 17,000 多行。

似乎瓶颈是row3 >= row4 - 因为代码中没有这部分,它会在 0.6-0.7 秒内运行。

(来自评论)

row(占位符列名)其实就是表中的id(主键,索引)列,就是我后面输出的结果集。我正在输出与查询中的参数匹配的 ID 数组,然后从该数组中选择一个随机 ID 以通过特定行上的最终查询收集数据。这是作为 rand() 的一种解决方法完成的。需要根据这些知识进行任何调整吗?

【问题讨论】:

以 EXPLAIN 开头 ...dev.mysql.com/doc/refman/5.7/en/explain.html 我认为您对行和列感到困惑 【参考方案1】:

17K 行并不是一个很小的结果集。大型结果集通常需要时间,因为将数据从 MySQL 服务器传递到请求它们的程序会产生开销。

您在row5 LIKE 'value' 中使用的'value' 的内容对查询性能有很大影响。如果'value'% 之类的通配符开头,您的查询会很慢。

话虽如此,您需要一个所谓的covering index。您尝试使用您创建的索引创建一个。它很接近但并不完美。

您的查询会根据row1row2row5 上的常量值进行过滤,因此这些列应该在您的索引中排在第一位。查询计划器可以随机访问您的索引到第一个匹配条目,然后顺序扫描索引,直到它到达最后一个匹配条目。这是最快的速度。

然后你想检查row3row4(比较它们)。这些列应该在索引中紧随其后。最后,如果您的查询的 SELECT 子句提到了您的 table 中的列的子集,您应该将这些列的其余部分放入索引中。因此,根据您问题中的查询,您的索引应该是

 CREATE INDEX indexName ON `table` (row1, row2, row5, row3, row4, row);

查询计划器将能够通过使用所谓的索引范围扫描扫描索引的子集来满足整个查询。这应该很快。

专业提示:不要用USE INDEX() 强迫查询计划者的手。相反,构建您的索引以有效地处理您的查询。

【讨论】:

row(占位符列名)实际上是表中的id(主键,索引)列,这是我稍后输出的结果集。我正在输出与查询中的参数匹配的 ID 数组,然后从该数组中选择一个随机 ID 以通过特定行上的最终查询收集数据。这是作为 rand() 的一种解决方法完成的。需要根据这些知识进行任何调整吗? 只是为了通知您。您的索引使其从 9 秒左右变为 2 秒左右 :-) @O。琼斯【参考方案2】:

索引不能用于比较同一张表中的两列(如果所有输出字段都包含在索引中,它最多可以用于索引扫描而不是表扫描),所以基本上有没有“正确”的方法来做到这一点。

如果您可以控制结构和填充表格的流程,您可以添加一个计算字段来保存两个字段之间的差异。然后将该字段添加到索引并调整您的查询以使用该字段而不是其他 2。

它并不漂亮,也没有提供很大的灵活性(例如,如果您想比较另一个字段,您还需要添加它等),但它确实完成了工作。

【讨论】:

不确定我将如何完成这项工作,因为我需要确保row3 始终大于或等于row4。据我所知,绘制这两者之间的数字不允许进行任何比较。如果我错了,请纠正我。 好吧,假设您调用计算字段 row6 并将其设置为 row3 - row4 的值。如果 row6 的值大于等于 0,则 row3 大于等于 row4。如果该值小于 0,则 row4 大于 row3。然后您可以将第 6 行添加到索引中,以便对其进行高效搜索。【参考方案3】:

(这是对http://mysql.rjweb.org/doc.php/random的改编)

让我们实际将随机化折叠到查询中。这将消除收集一堆 id、处理它们,然后再回到表中。它还可以避免需要额外的索引。

    查找最小和最大 id 值。 在最小值和最大值之间随机选择一个id。 向前扫描,查找 col1...col5 符合条件的第一行。

类似...

SELECT b.*   -- should replace with actual list of columns
    FROM
        ( SELECT id
            FROM tbl
            WHERE id >= ( SELECT MIN(id) +
                                 ( MAX(id) - MIN(id)
                                   - 22   -- somewhat avoids running off end
                                 ) * RAND()
                              FROM tbl )
              AND col1 = 0 ...  -- your various criteria
            ORDER BY id
            LIMIT 1
        ) AS a
    JOIN tbl AS b  USING(id);

优点/缺点:

可能比您可以设计的任何其他方法都快。 如果RAND() 在表中命中太晚,它将不返回任何内容。在这种(罕见的)情况下,再次运行查询,但从 0 开始。 id 中的大间隙将导致返回id 的偏差。 (上面的链接讨论了一些处理此类问题的方法。)

【讨论】:

该表有 590 万行,目前已提取 17,000 行。有些可能是 ID 200,有些可能是 ID 5,900,382 - 这不是问题吗? @里克 @Patrick - 是的,你提出了一个问题。例如,如果 id=3,123,456 是匹配 col1=0 AND ... 的最后一个 id,那么它将扫描很长时间(2M 行)而没有找到任何东西。到目前为止,所有替代方案都涉及扫描,但是许多行都有col1=0 AND col2=0(_no more_`)。有多少这样的行?

以上是关于由于特定部分导致的 MySQL 索引问题的主要内容,如果未能解决你的问题,请参考以下文章

面对 MySQL 查询索引失效,程序员的六大优化技巧!

SQL优化 · 经典案例 · 索引篇

MySQL 高级特性(二):数据表分区策略及优缺点分析

带有 spark.read.text 的 Spark 2.0 索引 3 处的预期方案特定部分:s3:错误

三高Mysql - Mysql索引和查询优化(偏实战部分)

由于数据中的“雪花问题”导致部分加载