MySQL主键未在查询中使用

时间:2018-04-08 22:56:43

标签: mysql query-performance

我有一个MySQL查询显然没有使用其中一个主键,这会减慢它的速度。

表格如下:

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

员工表有大约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替换USING(staff_ID)(production_role.staff_ID = staff_main.staff_ID),没有骰子。

谁能告诉我发生了什么? staff_main不会变得更小,所以如果我不能卷入该索引,这个查询会越来越滞后。

2 个答案:

答案 0 :(得分:0)

优化器告诉MySQL,在staff表上运行全表扫描并检索剩余信息更有利,而不是在剧集索引和角色ID上运行扫描并稍后加入员工。

您可以删除表扫描非常昂贵的提示,以排除表扫描。但是很可能优化器是正确的,并且在另一个方向上运行查询会花费更多。

在我看来,您需要这两个索引(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

那么,需要是什么?从哪里开始更方便?

数据来自两个地方:索引(让它们很快)和表格(让它们很慢)。

我们希望最小化检索到的元组数,但该数字是基于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

为了最佳地运行查询,我们需要尽快减少数据,因此我们需要知道情节索引或角色ID基数是否更大。可能是角色很少,剧集很多,这意味着限制在1000个中的一个剧集将使我们的数据减少1/1000,而对该角色的过滤将减少大约1/20。

因此,我们仅在pre.episode_index上使用WHERE运行查询。我们需要一个pre的索引,作为第一个字段,episode_index。 Pre是我们的主要表格。

然后我们加入pr。我们在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,因为它是从第一个表驱动的,而第二个是role_ID,以立即进一步限制工作。 我们尚未访问过两个表中的任何一个:我们只检查了索引

如果我们将第三列带有人员ID添加到pr索引,那么我们接下来需要的数据,即staff_ID,将全部包含在索引中,这将成为所谓的覆盖索引 - 我们根本不需要表格pr。你应该在EXPLAIN中看到类似“使用JOIN缓冲区”的东西,这意味着连接在优化的“爆发”中逐渐发生。

当然,EXPLAIN所做的估计仍将基于第一个WHERE的行数,因此它将是关于平均行数的平均数乘以平均角色数。这是最糟糕的情况估计:你很清楚剧集和角色的某些组合实际上可能没有返回任何东西。所以,你不应该让一个巨大的估计担心你。

此时我们有staff_main并且查询提供staff_ID作为其主键,因此我们不需要做任何事情:只需加入staff_main。为了衡量选择,请指定staff_ID来自pr,而不是staff_main。值是相同的,它可能没有任何改变,但是对pr.staff_ID的准备访问是保证和容易的(我们在覆盖索引中有它),我们不想混淆优化器,以防万一。

答案 1 :(得分:0)

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