【问题标题】:Simple query running slowly简单查询运行缓慢
【发布时间】:2020-03-03 22:38:21
【问题描述】:

我有以下疑问:

SELECT `assignments`.`id`
FROM `assignments` 
WHERE
  `assignments`.`account_id` = 742 
  AND `assignments`.`method` != 'stray' 
  AND (
    `assignments`.`judge_id` = 2349724 
    OR (
      `assignments`.`role_id` IN (234, 8745) 
      AND `assignments`.`judge_id` IS null
    )
  );

这个表目前有 660 万条记录,而且流量很大。我们最慢的查询是上面那个,即使有一个以 account_id、method、judge_id 和 role_id 为目标的索引,它也需要大约 0.5 秒才能运行。

查询确实使用了提供的索引,但似乎并没有给它带来太大的提升。

我可以在这里做些什么来改进查询并将其缩短到 100 毫秒以下? 660 万条记录真的不算多=\

我还想补充一点,如果我只是将查询限制在 account_id 子句(它有自己的索引),速度差不多。所以我真的很困惑。

以下是仅使用account_id的执行计划:

EXPLAIN select `assignments`.id FROM `assignments`WHERE `assignments`.`account_id` = 374;
+----+-------------+-------------+------------+------+----------------------------------------------------------------------+------------------------------+---------+-------+------+----------+-------------+
| id | select_type | table       | partitions | type | possible_keys                                                        | key                          | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------------+------------+------+----------------------------------------------------------------------+------------------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | assignments | NULL       | ref  | assignments_account_id_index,assignments_account_id_updated_at_index | assignments_account_id_index | 9       | const |  965 |   100.00 | Using index |
+----+-------------+-------------+------------+------+----------------------------------------------------------------------+------------------------------+---------+-------+------+----------+-------------+

创建表语法:

CREATE TABLE `assignments` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `key` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `batch` int(10) unsigned NOT NULL,
  `account_id` bigint(20) unsigned DEFAULT NULL,
  `season_id` bigint(20) unsigned DEFAULT NULL,
  `judge_id` bigint(20) unsigned DEFAULT NULL,
  `role_id` bigint(20) unsigned DEFAULT NULL,
  `entry_id` bigint(20) unsigned NOT NULL,
  `score_set_id` bigint(20) unsigned NOT NULL,
  `slug` char(8) COLLATE utf8_unicode_ci DEFAULT NULL,
  `method` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `original_method` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `status` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'none',
  `locked` tinyint(1) NOT NULL DEFAULT '0',
  `conflict_of_interest` tinyint(1) NOT NULL DEFAULT '0',
  `raw_score` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `raw_total` double NOT NULL DEFAULT '0',
  `weighted_score` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `weighted_total` double NOT NULL DEFAULT '0',
  `weight_sum` decimal(8,2) NOT NULL DEFAULT '0.00',
  `progress` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `consensus` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `top_pick_preference` tinyint(3) unsigned DEFAULT NULL,
  `top_pick_winner` tinyint(1) NOT NULL DEFAULT '0',
  `top_pick_rank` int(11) DEFAULT NULL,
  `total_votes` bigint(20) unsigned NOT NULL DEFAULT '0',
  `scored_at` timestamp NULL DEFAULT NULL,
  `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`),
  UNIQUE KEY `assignments_key_unique` (`key`),
  KEY `assignments_account_id_index` (`account_id`),
  KEY `assignments_judge_id_index` (`judge_id`),
  KEY `assignments_role_id_index` (`role_id`),
  KEY `assignments_entry_id_index` (`entry_id`),
  KEY `assignments_score_set_id_index` (`score_set_id`),
  KEY `assignments_season_id_index` (`season_id`),
  KEY `assignments_slug_index` (`slug`),
  KEY `assignments_status_index` (`status`),
  KEY `assignments_method_index` (`method`),
  KEY `assignments_original_method_index` (`original_method`),
  KEY `assignments_account_id_updated_at_index` (`account_id`,`updated_at`)
) ENGINE=InnoDB AUTO_INCREMENT=661994447 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

【问题讨论】:

  • MySql 利用复合索引的能力在第一个范围条件下结束。在这种情况下,(account_id, method, judge_id, role_id) 上的索引只能利用 account_id 和方法的索引,因为 != 是一个“范围”条件。
  • 问题是,即使我将查询限制为仅 account_id,如前所述 - 它仍然一样快/慢。
  • 关于方法方面,我应该简单地定义哪些值是有效的,而不是什么不是?
  • 能否贴出查询执行平面?
  • @Uueerdo - OR 使用同一列 变成了IN。否则,OR 对优化非常致命。 IN 的排名介于 = 和“范围”之间,具体取决于几件事。

标签: mysql sql indexing query-optimization query-performance


【解决方案1】:

由于范围条件会对 MySQL 利用索引的能力产生负面影响,因此有时可以使用 UNION(以查询语法的一些重复为代价):

SELECT a.id 
FROM `assignments` AS a
WHERE a.`account_id` = 742 
   AND a.`judge_id` = 2349724 
   AND a.`method` != 'stray' 
UNION ALL
SELECT a.id 
FROM `assignments` AS a
WHERE a.`account_id` = 742 
   AND a.`judge_id` IS NULL AND a.`role_id` IN (234, 8745)
   AND a.`method` != 'stray'
;

account_id, judge_id, method_idaccount_id, judge_id, role_id 上的复合索引将对上述查询的性能大有裨益。 ...如果我没记错的话,第一个可能有利于上半年,而第二个可能有利于下半年(但也有过度索引之类的事情)。

【讨论】:

  • 不是。事实上,它并没有做任何该死的事情。正如在最初的帖子中提到的,即使将查询限制为仅 account_id 也具有相同的查询性能..
  • 不是什么?您是否使用我建议的任何一个新索引尝试过这个? (像这样将其拆分成一个联合,可以不再将 Judge_id 值作为范围条件处理,从而让查询更好地利用从 account_id, judge_id 开始的索引。)
  • @Oddman 并且正如 GMB 在他的回答中指出的那样,看起来您没有任何复合索引;在这种情况下,单独的索引没有那么有用,因为 MySQL 在查询中每个表引用只能使用一个索引;它不会合并单个的。
  • 请重新阅读我刚才所说的:仅使用 account_id 子句,它具有相同的查询性能(并且 account_id 有自己的索引)。我确实尝试过复合索引。后一点就是为什么我这么糊涂。为什么只是通过 account_id 查询仍然很慢?
  • 您需要,并且可以使用,这两个索引:(account_id, judge_id, method)(account_id, judge_id, role_id)OR 是优化器杀手而不是范围。如果你不同意,让我们看看EXPLAIN SELECT...
【解决方案2】:

如果我是你,为了解决问题,我会看和做的事情:

检查您将获得的物品数量

SELECT count(*)
FROM `assignments` 
WHERE
  `assignments`.`account_id` = 742 
  AND `assignments`.`method` != 'stray' 
  AND (
    `assignments`.`judge_id` = 2349724 
    OR (
      `assignments`.`role_id` IN (234, 8745) 
      AND `assignments`.`judge_id` IS null
    )
  );

告诉我们您的特定查询应该产生多少条记录。

SELECT `assignments`.`id`
FROM `assignments` 
WHERE
  `assignments`.`account_id` = 742;

告诉您有多少作业与给定帐户相关联。如果计数明显快于实际选择,那可能意味着什么。此外,如果有很多记录,则可能需要很长时间才能将其加载到内存中并通过网络将其发送到另一台计算机。

检查桌子是否有任何东西很快

SELECT `assignments`.`id`
FROM `assignments` limit 0, 100;

如果速度很慢,那么您的网络可能有问题。

复制您的数据库

进行转储并重新创建您的数据库并在这个新创建的沙箱中运行您的查询,这样您就会看到其他命令是否会减慢您的速度。如果其他查询使您放慢速度,那么可能是写操作导致速度变慢。如果写锁让您放慢了速度,那么您可能希望将您的写操作分组并在特定时间一起执行。

制作适当的索引

在您的表上创建多维索引,使用您在where 子句中用作过滤器的字段,因为 UUeerdo 和 GMB 已经在他们的答案中提出了建议。

【讨论】:

  • 这是一个很好的回应,谢谢。计数的执行大致相同,没有真正的区别。主要问题是即使只是通过 account_id 查询也很慢,不管其他任何事情。有什么想法吗?
  • @Oddman 是的,我明白你的意思。计数执行大致相同的事实表明,与搜索本身相比,对内存的写入和读取作为负担是微不足道的。接下来我要检查的是运行像SELECT `assignments`.`id` FROM `assignments` limit 0, 100; 这样的查询加载任意记录是否表现良好。如果不是,那么这不是您的过滤器的问题,而是您的数据库的流量问题。
  • 啊哈!在网络中我是瓶颈吗?我会再做一些测试。谢谢。
  • @Oddman 至少我们需要一些测试来确认或反驳该假设。朝这个方向做实验本质上是有用的。如果是网络问题,那么您会找到要解决的问题。如果没有,那么您成功地缩小了问题空间。
【解决方案3】:

这可能不是一个完整的答案,但是:您没有正确的索引。复合索引不同于每列上的单个索引。

请考虑:

(account_id, judge_id, role_id, method, id)

由于AND/OR/IN,可能不会实际使用整个索引,但这至少给了查询计划者一个机会。您可能还想针对 Uueerdo 的 union all 查询(已投票)进行尝试。

【讨论】:

  • 感谢 GMB,请阅读整篇文章 :) 问题不在于索引,我尝试了许多索引,但没有一个改进它。此外,当仅使用 account_id 作为子句时,它也一样慢。
  • @Oddman:那么您可能应该显示此特定索引的执行计划(或 Uueerdo 建议的那些)。您展示的查询计划与您的实际查询几乎没有关系。
  • 我的主要观点是,即使只使用 account_id(和提供的计划),它也一样慢。不确定我能说得更具体吗?有什么建议吗?
  • 这里的索引是“覆盖”。 EXPLAIN 将通过说 Using index 来表示它。但是“覆盖”只能在性能上提供很小的提升。摆脱OR 是关键。
【解决方案4】:

我想到的第一件事是您不需要在查询中到处都有“分配”。

select id FROM `assignments`
WHERE `account_id` = 742
AND `method` != 'stray'
AND (`judge_id` = 2349724 
OR (`role_id` IN (234, 8745) 
AND `judge_id` IS null));

应该可以正常工作。 好的,这不能解决你的问题。

或许你可以先查询ID,再查询方法,像这样:

    select id FROM `assignments`
WHERE `account_id` = 742
    AND (`judge_id` = 2349724 
OR (`role_id` IN (234, 8745) 
AND `judge_id` IS null))
AND `method` != 'stray';

只是一个想法。

【讨论】:

  • 谢谢,迈克尔。如帖子中所述,即使我将其限制为仅 account_id (有自己的索引),它仍然太慢了。
  • MySQL(通常)不关心条件的顺序。
  • 事实证明,解析器将添加回表名!这可以通过EXPLAIN EXTENDED SELECT ...; SHOW WARNINGs; 看到。 (在较新的版本中,EXTENDED 可以省略。)
  • 补充瑞克所说的;虽然省略表名可能会使您的查询字符串更短,但它不会使您的查询过程更快。 请求通过网络发送的速度可能会稍微快一点,但它也可能会减慢服务器的解析速度,因为它必须识别哪个字段名称所指的是哪个表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-17
  • 2023-04-09
  • 2021-12-15
  • 1970-01-01
  • 1970-01-01
  • 2021-11-04
  • 1970-01-01
相关资源
最近更新 更多