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.