无法理解为什么 mysql 查询这么慢

Cannot understand why mysql query is so slow

我有一个非常慢的遗留查询。我将展示查询,然后展示它的背景。 查询需要大约 10 秒,这慢得离谱。解释给我:

查询:

select  staff.id as Id, 
        staff.eid as AccountId, 
        staff.Surname 
from staff 
LEFT JOIN app_roles ON (app_roles.app_staff_id = staff.id )
where staff.eid = 7227 
AND app_roles.application_id = '1' 
and staff.last_modified > '2022-05-11 13:15:21Z'

Staff table 包含 280k 行,app_roles 包含 644k 行。开斋节 7727 - 87 行的员工行。 app_roles 匹配员工 ID 的行 - 75 行

Table 结构:

CREATE TABLE `app_roles` (
  `application_id` varchar(40) NOT NULL,
  `app_staff_id` varchar(40) NOT NULL,
  `role` varchar(40) NOT NULL,
  PRIMARY KEY (`application_id`,`app_staff_id`),
  KEY `application_id` (`application_id`),
  KEY `app_staff_id` (`app_staff_id`)
) ENGINE=InnoDB

CREATE TABLE `staff` (
  `eid` int NOT NULL,
  `id` varchar(40) NOT NULL,
  `forename` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `surname` varchar(150) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  last_nodified DATETIME NOT NULL,
  ... columns omitted for simplicity
  PRIMARY KEY (`eid`,`id`),
  KEY `email` (`email`),
  KEY `app_login` (`app_login`),
  KEY `app_passwd` (`app_password`),
  KEY `id` (`id`),
  KEY `eid` (`eid`)
) ENGINE=InnoDB


+----+-------------+-----------+------------+--------+-------------------------------------+----------------+---------+---------------------------------------+--------+----------+--------------------------+
| id | select_type | table     | partitions | type   | possible_keys                       | key            | key_len | ref                                   | rows   | filtered | Extra                    |
+----+-------------+-----------+------------+--------+-------------------------------------+----------------+---------+---------------------------------------+--------+----------+--------------------------+
|  1 | SIMPLE      | app_roles | NULL       | ref    | PRIMARY,application_id,app_staff_id | application_id | 42      | const                                 | 330114 |   100.00 | Using where; Using index |
|  1 | SIMPLE      | staff     | NULL       | eq_ref | PRIMARY,id,eid                      | PRIMARY        | 126     | const,inventry.app_roles.app_staff_id |      1 |    33.33 | Using where              |
+----+-------------+-----------+------------+--------+-------------------------------------+----------------+---------+---------------------------------------+--------+----------+--------------------------+

我不明白为什么左连接和 where 没有过滤掉行,为什么索引没有帮助。

在所有其他条件相同的情况下,MySQL 喜欢通过主键查找进行连接。它对此有强烈的偏好,因为主键查找比辅助键查找更有效。

它甚至可以更改联接的顺序以满足此偏好。内连接是可交换的,因此优化器可以先访问 table,然后连接到另一个。

但是您使用了 LEFT [OUTER] JOIN,那么如何将其优化为内部连接?您在 WHERE 子句中写了一个条件 app_roles.application_id = '1'。如果您在左外部联接的右侧 table 上测试 non-NULL 值,它会消除任何会使该联接成为外部联接的行。它实际上是一个内部连接。因此,优化器可以自由地对连接中的 table 重新排序。

两种联接顺序都会导致使用主键查找进行联接。在这两种情况下,查找的第一列都基于查询中的常量条件。查找的第二列是第一列 table.

的引用

所以优化器进退两难了。它可以选择任一连接顺序,并且都满足主键查找的偏好。所以它任意选择一个。

失败之处在于它显然没有考虑到 application_id 上的条件导致它检查超过 330k 行。要么优化器对此成本视而不见,要么 table 统计数据不是最新的并且正在愚弄优化器。

您可以刷新 table 统计数据。这很容易做到,并且对 运行 系统的影响很小,因此您不妨这样做以排除不良统计信息导致不良查询优化的可能性。

ANALYZE TABLE app_roles;
ANALYZE TABLE staff;

然后再次尝试您的查询。

如果它仍然选择了错误的优化策略,您可以使用 join hint 强制它使用与您在查询中写入的内容相匹配的连接顺序。

select  id as Id, 
        eid as AccountId, 
        Surname 
from staff 
STRAIGHT_JOIN app_roles ON (app_roles.app_staff_id = staff.id )
where staff.eid = 7227 
AND app_roles.application_id = '1' 
and last_modified > '2022-05-11 13:15:21Z'

可能还有一种方法可以将 last_modified 合并到索引中,但我不知道它属于哪个 table。

我假设您对字符集/排序规则有疑问。确保您要加入的字段匹配。为了验证这一点,运行 :

SHOW FULL COLUMNS FROM staff;
SHOW FULL COLUMNS FROM app_roles;

更具体地说,确保 app_roles.app_staff_id 和 staff.id 是同一类型。

这些 'composite' 和 'covering' 索引应该有所帮助:

staff:  INDEX(eid, last_modified, id,  Surname)
app_roles:  INDEX(application_id, app_staff_id)

去掉 DATETIME 文字上的 Z; MySQL不明白这样。