两个索引良好的表的单个 INNER JOIN 需要超过一分钟才能运行

Posted

技术标签:

【中文标题】两个索引良好的表的单个 INNER JOIN 需要超过一分钟才能运行【英文标题】:Single INNER JOIN of two well-indexed tables takes more than a minute to run 【发布时间】:2018-06-04 09:59:02 【问题描述】:

我有一个查询需要大约 90 秒才能运行,即使表应该有正确的索引。我不明白为什么。

我使用的是 mysql,表是 InnoDB。

这是查询:

SELECT count(*)
FROM `following_lists` fl INNER JOIN users u 
ON fl.user_uuid = u.user_uuid
WHERE fl.following_query_id = 1000010 AND u.status <= 2

我希望这个查询从表 following_lists 开始,根据 WHERE 条件抓取大约 4K 条记录,通过主键将这些记录连接到表 users,检查用户表中字段的值,并返回结果记录的计数。为什么需要这么长时间?难道是因为我加入表格的两个字段是 CHAR(40) 而不是整数?

这些是涉及的表及其索引:

CREATE TABLE `users` ( 
  `user_uuid` CHAR(40) NOT NULL, 
  `status` TINYINT UNSIGNED NOT NULL, 
  ...

  PRIMARY KEY (`user_uuid`), 
  ...
)

CREATE TABLE `following_lists` ( 
  `following_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `following_query_id` INT UNSIGNED NOT NULL,
  `user_uuid` CHAR(40) NOT NULL,

  PRIMARY KEY (`following_id`), 
  KEY `query_id` (`following_query_id`),
  KEY `user_uuid` (`user_uuid`)
)

这是解释查询的输出:

+----+-------------+-------+--------+--------------------+----------+---------+--------------+------+-------------+
| id | select_type | table |  type  |   possible_keys    |   key    | key_len |     ref      | rows |    Extra    |
+----+-------------+-------+--------+--------------------+----------+---------+--------------+------+-------------+
|  1 | SIMPLE      | fl    | ref    | query_id,user_uuid | query_id |       4 | const        | 3718 |             |
|  1 | SIMPLE      | u     | eq_ref | PRIMARY            | PRIMARY  |     160 | fl.user_uuid |    1 | Using index |
+----+-------------+-------+--------+--------------------+----------+---------+--------------+------+-------------+

更多细节:

following_lists 表有大约 25k 行,但只有 3718 行有fl.following_query_id = 1000010

users 有大约 160k 行,但在连接中应该只选择 3718。只有 40 条记录同时满足这两个条件 fl.following_query_id = 1000010 AND u.status &lt;= 2

即使我删除条件AND u.status &lt;= 2,查询也很慢。

【问题讨论】:

如果是 CHAR(40),我不会感到惊讶。对于整数值,需要进行 1 次比较;使用该 CHAR(40),最多需要进行 40 次比较。实际需要多少取决于 CHAR(40) 中的实际值,如果这些值的前 X 个字符都相同,则至少需要那么多。另外,如果我没记错的话,MySQL 的 CHAR 索引也不会覆盖整个字符串,如果是前导字符,只会覆盖一定的数字。 两列是否使用相同的排序规则和字符集? 查看其他 SO 问题的已接受答案。它可能会为您提供一些关于使用 UUID 提高性能的方法的线索。 ***.com/questions/2365132/uuid-performance-in-mysql/… @SamM 是的,两个表都使用CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci。感谢您的链接,我认为问题肯定出在 UUID 上。当我有时间时,我可能会尝试使用 auto_increment 字段复制数据库,看看问题是否仍然存在。 【参考方案1】:

“有正确的索引”——死的放弃。

如果您使用的是 MyISAM,请不要。相反,切换到 InnoDB。

您需要following_lists.id 吗? (following_query_id, user_uuid) 是唯一的吗?如果是这样,请将它们设为PRIMARY KEY

如果以上都做不到,就改

KEY `query_id` (`following_query_id`)

INDEX(following_query_id, user_uuid)

UUIDs 效率极低,尤其是当不必要地声明utf8mb4CHAR 的大小超出必要时。更改为CHAR(36) CHARACTER SET ascii。 (注意 `EXPLAIN 中的“160”显着缩小。)

更多关于 UUID 不利于性能的原因:http://mysql.rjweb.org/doc.php/uuid

你有多少内存? innodb_buffer_pool_size 的设置是什么? (听起来太低了。)

有关索引的更多信息:http://mysql.rjweb.org/doc.php/index_cookbook_mysql

【讨论】:

以上是关于两个索引良好的表的单个 INNER JOIN 需要超过一分钟才能运行的主要内容,如果未能解决你的问题,请参考以下文章

DQL查询数据----联表查询(left/right/inner join 区别)

DQL查询数据----联表查询(left/right/inner join 区别)

如何从跨两个 DATE 列的 INNER JOIN 创建 MIN 和 MAX 日期列,其中每个 DATE 列来自单独的表 BigQuery

数据库中INNER JOIN的意思。

IN SQL INNER JOIN 可以添加两个不同列的表吗?

表的基本查询语句及使用连表(inner joinleft join)子查询