MySQL 查询中未使用主键
MySQL primary key not being used in query
我有一个 MySQL 查询显然没有使用主键之一,这会减慢它的速度。
table 看起来像这样:
staff_main:
int staff_ID (the primary key)
string name
production_role:
int row_index (primary key, auto-incremented)
int staff_ID (indexed)
int production_ID (indexed)
int role_ID
production_role_episodes:
int row_index (primary key, autoincremented)
int match_index (foreign key to production_role.row_index)
int episode_index (foreign key to episode_info.episode_index)
episode_info:
int episode_index (primary key)
int production_ID
...other info not used here
查询看起来像这样。它旨在获取剧集的索引 ID 和角色的 ID,并查找在指定剧集中担任该角色的所有工作人员。
SELECT staff_main.staff_ID AS sid,
staff_main.name AS name
FROM production_role_episodes
JOIN production_role ON (production_role.row_index = production_role_eps.match_index)
JOIN staff_main USING (staff_ID)
WHERE production_role_eps.episode_index = {episode}
AND production_role.role_ID = {role}
ORDER BY name
工作人员 table 有大约 9000 行,这开始变慢了。 EXPLAIN 生成以下内容:
+----+-------------+---------------------+--------+------------------+----------+---------+----------------------------------------------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+--------+------------------+----------+---------+----------------------------------------------+------+---------------------------------+
| 1 | SIMPLE | staff_main | ALL | PRIMARY | NULL | NULL | NULL | 9327 | Using temporary; Using filesort |
| 1 | SIMPLE | production_role | ref | PRIMARY,staff_ID | staff_ID | 4 | test_prod_db.staff_main.staff_ID | 2 | Using where |
| 1 | SIMPLE | production_role_eps | eq_ref | PRIMARY | PRIMARY | 8 | test_prod_db.production_role.row_index,const | 1 | Using index |
+----+-------------+-------====----------+--------+------------------+----------+---------+----------------------------------------------+------+---------------------------------+
它显然没有使用 staff_main.staff_ID 作为键,尽管这是一个主键。我试图通过向 staff_main JOIN 添加 USE INDEX(PRIMARY) 来强制执行它,但根据 EXPLAIN,它仍然没有使用密钥。我尝试重新排列 JOIN,我尝试用 ON (production_role.staff_ID = staff_main.staff_ID) 替换 USING (staff_ID),没有骰子。
谁能告诉我这是怎么回事? staff_main 不会变得更小,所以如果我无法收录该索引,此查询只会越来越滞后。
优化器告诉 MySQL 运行 对员工 table 进行全面 table 扫描并检索剩余信息会更有利,而不是比 运行扫描剧集索引和角色 ID 并稍后加入工作人员。
您可以提示 table 扫描非常昂贵,以排除 table 扫描。但很有可能优化器是正确的,运行从另一个方向执行查询会花费更多。
在我看来,您需要这两个索引(role_ID 在您的描述中没有索引),具有以下确切结构:
CREATE INDEX production_role_ndx ON production_role(role_ID, row_index, staff_ID);
CREATE INDEX production_role_eps_ndx ON production_role_episodes(episode_index, match_index);
您似乎不需要那么多,对于这个查询(但也许对于其他查询?),这些其他的:
int staff_ID (indexed)
int production_ID (indexed)
更详细的解释
您的查询(已缩短)是:
SELECT staff_ID, name
FROM pre
JOIN pr ON (pr.row_index = pre.match_index)
JOIN sm ON (sm.staff_ID = pr.staff_ID)
WHERE pre.episode_index = {episode}
AND pr.role_ID = {role}
ORDER BY name
那么,需要什么?从哪里开始比较方便?
数据来自两个地方:索引(获取它们很快)和tables(获取它们很慢)。
我们希望尽量减少检索到的元组数量,但该数量是基于 JOIN 几何形状的估计值。然后,我们希望从索引中检索更多可能的信息,而不是检索冗余信息。
以上查询要求:
sm.staff_ID, sm name for the SELECT
pr.row_index, pre.match_index, sm.staff_ID, pr.staff_ID for the JOIN
pre.episode_index, pr.role_ID for the WHERE
为了运行优化查询,我们需要尽快减少数据,所以我们需要知道episode index或者role ID cardinality哪个更大。很可能角色很少而剧集很多,这意味着限制为 1000 集中的一集会使我们的数据减少 1/1000,而对角色进行过滤可能会减少 1/20。
所以我们 运行 单独在 pre.episode_index 上使用 WHERE 进行查询。我们需要一个 pre 索引,作为第一个字段,episode_index。 Pre是我们的主要table.
然后我们加入公关。我们在 pr.role_ID 上也有一个过滤器。我们如何找到 pr 的行?
pr.row_index = pre.match_index
pr.role_ID = {role}
JOIN pr ON (pr.row_index = pre.match_index AND pr_role_ID = {role})
所以我们想首先在 row_index 上索引 pr,因为它是从第一个 table 和第二个 role_ID 驱动的,以立即进一步限制工作。 我们还没有访问两个 table 中的任何一个:我们只检查了索引。
如果我们将带有员工 ID 的第三列添加到 pr 索引中,那么我们接下来需要的数据,即 staff_ID,将全部包含在索引中,这就是所谓的覆盖索引 - 我们也根本不需要 table pr。您应该在 EXPLAIN 中看到类似 "using JOIN buffer" 的内容,这意味着连接在优化的 "bursts".
中零碎地发生
当然,EXPLAIN 所做的估计 仍将基于第一个 WHERE 的行数,因此大约是剧集行数的平均数乘以角色的平均数量。这是最坏的情况估计:你很清楚剧集和角色的某些组合实际上可能 return 什么都没有。所以,你不应该让一个巨大的估计让你担心。
此时我们有 staff_main 并且查询提供 staff_ID 作为其主键,所以我们什么都不用做:只需加入 staff_main。为了更好地衡量 select,请指定 staff_ID 来自 pr,而不是 staff_main。值是相同的,它可能没有任何改变,但是可以保证并且容易地访问 pr.staff_ID(我们在覆盖索引中有它),我们不想混淆优化器,以防万一。
是production_role_episodes
吗?还是production_role_eps
?我假设这是对查询的有效重构:
SELECT sm.staff_ID AS sid, sm.name AS name
FROM production_role_episodes AS pre
JOIN production_role AS pr ON (pr.row_index = pre.match_index)
JOIN staff_main AS sm USING (staff_ID)
WHERE pre.episode_index = {episode}
AND pr.role_ID = {role}
ORDER BY name
我会添加这些索引:
pre: (episode_index, match_index)
pr: (role_ID, row_index, staff_ID)
sm: (staff_id) -- already the PK
至于为什么没有使用PK,我需要查看数据类型(和其他东西);请提供 SHOW CREATE TABLE
.
我有一个 MySQL 查询显然没有使用主键之一,这会减慢它的速度。
table 看起来像这样:
staff_main:
int staff_ID (the primary key)
string name
production_role:
int row_index (primary key, auto-incremented)
int staff_ID (indexed)
int production_ID (indexed)
int role_ID
production_role_episodes:
int row_index (primary key, autoincremented)
int match_index (foreign key to production_role.row_index)
int episode_index (foreign key to episode_info.episode_index)
episode_info:
int episode_index (primary key)
int production_ID
...other info not used here
查询看起来像这样。它旨在获取剧集的索引 ID 和角色的 ID,并查找在指定剧集中担任该角色的所有工作人员。
SELECT staff_main.staff_ID AS sid,
staff_main.name AS name
FROM production_role_episodes
JOIN production_role ON (production_role.row_index = production_role_eps.match_index)
JOIN staff_main USING (staff_ID)
WHERE production_role_eps.episode_index = {episode}
AND production_role.role_ID = {role}
ORDER BY name
工作人员 table 有大约 9000 行,这开始变慢了。 EXPLAIN 生成以下内容:
+----+-------------+---------------------+--------+------------------+----------+---------+----------------------------------------------+------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+--------+------------------+----------+---------+----------------------------------------------+------+---------------------------------+
| 1 | SIMPLE | staff_main | ALL | PRIMARY | NULL | NULL | NULL | 9327 | Using temporary; Using filesort |
| 1 | SIMPLE | production_role | ref | PRIMARY,staff_ID | staff_ID | 4 | test_prod_db.staff_main.staff_ID | 2 | Using where |
| 1 | SIMPLE | production_role_eps | eq_ref | PRIMARY | PRIMARY | 8 | test_prod_db.production_role.row_index,const | 1 | Using index |
+----+-------------+-------====----------+--------+------------------+----------+---------+----------------------------------------------+------+---------------------------------+
它显然没有使用 staff_main.staff_ID 作为键,尽管这是一个主键。我试图通过向 staff_main JOIN 添加 USE INDEX(PRIMARY) 来强制执行它,但根据 EXPLAIN,它仍然没有使用密钥。我尝试重新排列 JOIN,我尝试用 ON (production_role.staff_ID = staff_main.staff_ID) 替换 USING (staff_ID),没有骰子。
谁能告诉我这是怎么回事? staff_main 不会变得更小,所以如果我无法收录该索引,此查询只会越来越滞后。
优化器告诉 MySQL 运行 对员工 table 进行全面 table 扫描并检索剩余信息会更有利,而不是比 运行扫描剧集索引和角色 ID 并稍后加入工作人员。
您可以提示 table 扫描非常昂贵,以排除 table 扫描。但很有可能优化器是正确的,运行从另一个方向执行查询会花费更多。
在我看来,您需要这两个索引(role_ID 在您的描述中没有索引),具有以下确切结构:
CREATE INDEX production_role_ndx ON production_role(role_ID, row_index, staff_ID);
CREATE INDEX production_role_eps_ndx ON production_role_episodes(episode_index, match_index);
您似乎不需要那么多,对于这个查询(但也许对于其他查询?),这些其他的:
int staff_ID (indexed)
int production_ID (indexed)
更详细的解释
您的查询(已缩短)是:
SELECT staff_ID, name
FROM pre
JOIN pr ON (pr.row_index = pre.match_index)
JOIN sm ON (sm.staff_ID = pr.staff_ID)
WHERE pre.episode_index = {episode}
AND pr.role_ID = {role}
ORDER BY name
那么,需要什么?从哪里开始比较方便?
数据来自两个地方:索引(获取它们很快)和tables(获取它们很慢)。
我们希望尽量减少检索到的元组数量,但该数量是基于 JOIN 几何形状的估计值。然后,我们希望从索引中检索更多可能的信息,而不是检索冗余信息。
以上查询要求:
sm.staff_ID, sm name for the SELECT
pr.row_index, pre.match_index, sm.staff_ID, pr.staff_ID for the JOIN
pre.episode_index, pr.role_ID for the WHERE
为了运行优化查询,我们需要尽快减少数据,所以我们需要知道episode index或者role ID cardinality哪个更大。很可能角色很少而剧集很多,这意味着限制为 1000 集中的一集会使我们的数据减少 1/1000,而对角色进行过滤可能会减少 1/20。
所以我们 运行 单独在 pre.episode_index 上使用 WHERE 进行查询。我们需要一个 pre 索引,作为第一个字段,episode_index。 Pre是我们的主要table.
然后我们加入公关。我们在 pr.role_ID 上也有一个过滤器。我们如何找到 pr 的行?
pr.row_index = pre.match_index
pr.role_ID = {role}
JOIN pr ON (pr.row_index = pre.match_index AND pr_role_ID = {role})
所以我们想首先在 row_index 上索引 pr,因为它是从第一个 table 和第二个 role_ID 驱动的,以立即进一步限制工作。 我们还没有访问两个 table 中的任何一个:我们只检查了索引。
如果我们将带有员工 ID 的第三列添加到 pr 索引中,那么我们接下来需要的数据,即 staff_ID,将全部包含在索引中,这就是所谓的覆盖索引 - 我们也根本不需要 table pr。您应该在 EXPLAIN 中看到类似 "using JOIN buffer" 的内容,这意味着连接在优化的 "bursts".
中零碎地发生当然,EXPLAIN 所做的估计 仍将基于第一个 WHERE 的行数,因此大约是剧集行数的平均数乘以角色的平均数量。这是最坏的情况估计:你很清楚剧集和角色的某些组合实际上可能 return 什么都没有。所以,你不应该让一个巨大的估计让你担心。
此时我们有 staff_main 并且查询提供 staff_ID 作为其主键,所以我们什么都不用做:只需加入 staff_main。为了更好地衡量 select,请指定 staff_ID 来自 pr,而不是 staff_main。值是相同的,它可能没有任何改变,但是可以保证并且容易地访问 pr.staff_ID(我们在覆盖索引中有它),我们不想混淆优化器,以防万一。
是production_role_episodes
吗?还是production_role_eps
?我假设这是对查询的有效重构:
SELECT sm.staff_ID AS sid, sm.name AS name
FROM production_role_episodes AS pre
JOIN production_role AS pr ON (pr.row_index = pre.match_index)
JOIN staff_main AS sm USING (staff_ID)
WHERE pre.episode_index = {episode}
AND pr.role_ID = {role}
ORDER BY name
我会添加这些索引:
pre: (episode_index, match_index)
pr: (role_ID, row_index, staff_ID)
sm: (staff_id) -- already the PK
至于为什么没有使用PK,我需要查看数据类型(和其他东西);请提供 SHOW CREATE TABLE
.