MySQL:如何优化导致负载非常高的某个SELECT语句?

Bla*_*bam 3 mysql performance index query select

有一个包含 25.000.000 个条目的表,我有以下查询,该查询每 2 秒启动一次,这会导致非常高的负载(在 Amazon AWS 中高达 40 AAS)。执行时间需要20秒到5分钟,甚至会导致用户浏览器超时,掉线率很高。

SELECT COUNT ( * ) AS `chk` 
  FROM ( SELECT `item_id` 
           FROM `items` 
          WHERE `item_status` IN (...) 
            AND `item_type` = ? 
            AND `user_id` != ? 
            AND `item_name` IN (...) 
          LIMIT 3 
       ) AS OTHERS
;
Run Code Online (Sandbox Code Playgroud)

索引优化已完成 - 、、 和user_id均已item_name建立索引(每列一个索引)。item_typeitem_status

更多信息:

  • 一个用户拥有 1 - 1.000.000 百万条条目
  • item_name 是varchar128
  • item_type 的基数为 7
  • item_status 的基数也是 7
  • 只需知道是否有三个或更多匹配项

请注意,在大约 50% 的情况下,MySQL 必须检查完整的表,因为找到的项目少于 3 个。因此,该限制仅在商品数量超过 3 件的情况下才有用。

虽然我对某些结果进行 Redis 缓存,但对于此查询来说这是不可能的,因为始终需要精确的结果。随着数据库以每秒约 1 个条目的速度增长,查询性能很快就会变差。

虽然过去我可以通过索引解决大多数问题,但现在我遇到了一个严重的问题。想过用一些触发器或视图来解决问题,但我不确定这是否有帮助?在高度活跃的生产数据库中,在不知道是否能解决问题的情况下进行此类更改是危险的。

我向专业人士提出的问题是:如何使用 MySQL 解决该问题?

小智 9

您可以尝试使用复合索引。

\n

您的索引可能如下所示:

\n
INDEX (item_type, item_name, item_status, user_id)\n
Run Code Online (Sandbox Code Playgroud)\n

该索引应该允许 MySQL 有效地查找所有行,而不必从实际表中查找任何行。

\n

列的顺序在这里很重要:

\n
    \n
  • item_type首先,因为您在列上有一个相等过滤器。
  • \n
  • item_name并排item_status在第二和第三位,因为两者都使用IN. 列的顺序可以更改,但因为item_name基数可能比item_status首先使用它可能要快一些。
  • \n
  • user_id排在最后是因为它使用了不等式过滤器,而该过滤器无法通过索引有效处理。
  • \n
\n

一般来说,MySQL 每个表\xc2\xb9 只会使用 1 个索引,因此拥有超过 1 个索引并没有什么帮助。

\n

另外,如果索引看起来不能过滤足够的行(有足够的行),MySQL 也不会使用索引。查询计划器根据表和索引统计信息计算出这样做的原因是,当使用索引时,MySQL仍然需要查找表中的原始行(除非你有一个覆盖索引\xc2\xb2),这有点昂贵,所以使用索引来查找很多行可以实际上比全表扫描慢。

\n

考虑到表中的行数,大多数(如果不是全部)4 个现有索引可能每个值的行数过多,并且 MySQL 甚至不会考虑此查询。尽管这实际上取决于数据分布和基数。

\n

您可以检查使用了哪些索引以及它们是否覆盖或不使用EXPLAIN. 例如

\n
EXPLAIN SELECT COUNT ( * ) AS `chk` FROM ( SELECT `item_id` FROM `items` WHERE `item_status` IN (...) AND `item_type` = ? AND `user_id` != ? AND `item_name` IN (...) LIMIT 3 ) AS OTHERS;\n
Run Code Online (Sandbox Code Playgroud)\n

如果您使用 MySQL 8,您还可以使用它EXPLAIN ANALYZE来实际了解 MySQL 如何通过计时读取和过滤数据。

\n

Rick James 写了一篇很棒的文档,介绍如何为 SELECT 构建最佳索引,我推荐您阅读。他还写了一篇关于复合(复合)索引的文档,我也可以推荐。

\n

将来,请尝试至少提供表架构和索引(您可以用于SHOW CREATE TABLE ?此)以及运行有问题的查询的输出EXPLAIN以及 MySQL 版本。这减少了我们必须进行的猜测,并使我们能够给出更具体、更好的答案。

\n

\xc2\xb9 MySQL 可以使用超过 1 个索引的优化称为索引合并优化,但它只在极少数情况下有效,应该避免。

\n

\xc2\xb2 覆盖索引是包含查询中使用的所有列的索引。由于所有列都已经在索引中,MySQL 不需要从表本身获取其他列。我在这个答案中提出的索引是覆盖索引的一个示例。

\n