我有一个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不会变得更小,所以如果我不能卷入该索引,这个查询会越来越滞后。
答案 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
。