无法理解为什么 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不明白这样。
我有一个非常慢的遗留查询。我将展示查询,然后展示它的背景。 查询需要大约 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不明白这样。