EXPLAIN 输出格式
EXPLAIN 语句提供有关 MySQL 如何执行语句的信息。EXPLAIN 适用于 SELECT、DELETE、INSERT、REPLACE 和 UPDATE 语句。
EXPLAIN 为 SELECT 语句中使用的每个表返回一行信息。它按照 MySQL 在处理语句时读取它们的顺序列出输出中的表。这意味着 MySQL 从第一个表中读取一行,然后在第二个表中找到匹配的行,然后在第三个表中查找,依此类推。当所有表都处理完毕后,MySQL 输出所选列并回溯表列表,直到找到一个有更多匹配行的表。从该表中读取下一行,然后继续处理下一个表。
EXPLAIN 输出列
本节介绍 EXPLAIN 生成的输出列。后面的部分提供了有关 type 和 Extra 列的附加信息。
EXPLAIN 的每个输出行提供有关一个表的信息。每行包含 表 10.1,"EXPLAIN 输出列" 中总结的值,并在表后提供更详细的描述。列名显示在表的第一列中;第二列提供使用 FORMAT=JSON 时显示的等效属性名称。
表 10.1 EXPLAIN 输出列
| 列 | JSON 名称 | 含义 |
|---|---|---|
id |
select_id |
SELECT 标识符 |
select_type |
无 | SELECT 类型 |
table |
table_name |
输出行的表 |
partitions |
partitions |
匹配的分区 |
type |
access_type |
连接类型 |
possible_keys |
possible_keys |
可供选择的索引 |
key |
key |
实际选择的索引 |
key_len |
key_length |
所选键的长度 |
ref |
ref |
与索引比较的列 |
rows |
rows |
要检查的行数估计值 |
filtered |
filtered |
按表条件过滤的行百分比 |
Extra |
无 | 附加信息 |
EXPLAIN 连接类型
EXPLAIN 输出的 type 列描述了表是如何连接的。在 JSON 格式的输出中,这些作为 access_type 属性的值找到。以下列表描述连接类型,按从最佳到最差的顺序排列:
-
表只有一行(=系统表)。这是
const连接类型的特例。 -
表最多有一个匹配行,在查询开始时读取。因为只有一行,所以该行中的列值可以被优化器的其余部分视为常量。
const表非常快,因为它们只读取一次。当你将
PRIMARY KEY或UNIQUE索引的所有部分与常量值进行比较时,使用const。在以下查询中,tbl_name 可以用作const表:SELECT * FROM tbl_name WHERE primary_key=1; SELECT * FROM tbl_name WHERE primary_key_part1=1 AND primary_key_part2=2; -
对于前一个表的每一行组合,从此表中读取一行。除了
system和const类型之外,这是最好的连接类型。当连接使用索引的所有部分且索引是PRIMARY KEY或UNIQUE NOT NULL索引时使用。eq_ref可用于使用=运算符比较的索引列。比较值可以是常量或使用在此表之前读取的表中的列的表达式。在以下示例中,MySQL 可以使用eq_ref连接来处理 ref_table:SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1; -
对于前一个表的每一行组合,从此表中读取所有具有匹配索引值的行。如果连接仅使用键的最左前缀或键不是
PRIMARY KEY或UNIQUE索引(换句话说,如果连接不能基于键值选择单行),则使用ref。如果使用的键只匹配几行,这是一个好的连接类型。ref可用于使用=或<=>运算符比较的索引列。在以下示例中,MySQL 可以使用ref连接来处理 ref_table:SELECT * FROM ref_table WHERE key_column=expr; SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column; SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1; -
使用
FULLTEXT索引执行连接。 -
此连接类型类似于
ref,但另外 MySQL 会额外搜索包含NULL值的行。此连接类型优化最常用于解析子查询。在以下示例中,MySQL 可以使用ref_or_null连接来处理 ref_table:SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL; -
此连接类型表示使用了索引合并优化。在这种情况下,输出行中的
key列包含使用的索引列表,key_len包含使用的索引的最长键部分列表。有关更多信息,请参阅 第 10.2.1.3 节,"索引合并优化"。 -
此类型替换以下形式的某些
IN子查询的eq_ref:value IN (SELECT primary_key FROM single_table WHERE some_expr)unique_subquery只是一个索引查找函数,完全替换子查询以提高效率。 -
此连接类型类似于
unique_subquery。它替换IN子查询,但适用于以下形式子查询中的非唯一索引:
value IN (SELECT key_column FROM single_table WHERE some_expr)
- [`range`](https://dev.mysql.com/doc/refman/9.6/en/explain-output.html#jointype_range)
仅检索给定范围内的行,使用索引选择行。输出行中的 `key` 列指示使用哪个索引。`key_len` 包含使用的最长键部分。此类型的 `ref` 列为 `NULL`。
当使用任何 [`=`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_equal)、[`<>`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_not-equal)、[`>`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_greater-than)、[`>=`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_greater-than-or-equal)、[`<`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_less-than)、[`<=`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_less-than-or-equal)、[`IS NULL`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_is-null)、[`<=>`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_equal-to)、[`BETWEEN`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_between)、[`LIKE`](https://dev.mysql.com/doc/refman/9.6/en/string-comparison-functions.html#operator_like) 或 [`IN()`](https://dev.mysql.com/doc/refman/9.6/en/comparison-operators.html#operator_in) 运算符将键列与常量进行比较时,可以使用 [`range`](https://dev.mysql.com/doc/refman/9.6/en/explain-output.html#jointype_range):
```sql
SELECT * FROM tbl_name
WHERE key_column = 10;
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name
WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
-
index连接类型与ALL相同,只是扫描索引树。这有两种情况:- 如果索引是查询的覆盖索引,并且可用于满足表所需的所有数据,则只扫描索引树。在这种情况下,
Extra列显示Using index。仅索引扫描通常比ALL更快,因为索引的大小通常小于表数据。 - 使用从索引读取来按索引顺序查找数据行执行全表扫描。
Uses index不出现在Extra列中。
当查询仅使用属于单个索引的列时,MySQL 可以使用此连接类型。
- 如果索引是查询的覆盖索引,并且可用于满足表所需的所有数据,则只扫描索引树。在这种情况下,
-
对前一个表的每一行组合执行全表扫描。如果表是第一个未标记为
const的表,这通常不好,在所有其他情况下通常非常糟糕。通常,你可以通过添加索引来避免ALL,这些索引允许基于常量值或早期表中的列值从表中检索行。
EXPLAIN 额外信息
EXPLAIN 输出的 Extra 列包含有关 MySQL 如何解析查询的附加信息。以下列表解释可以出现在此列中的值。每个项目还指示 JSON 格式输出中哪个属性显示 Extra 值。对于其中一些,有特定的属性。其他则显示为 message 属性的文本。
如果你想使查询尽可能快,请注意 Extra 列值为 Using filesort 和 Using temporary,或在 JSON 格式的 EXPLAIN 输出中,using_filesort 和 using_temporary_table 属性等于 true。
Backward index scan(JSON:backward_index_scan)优化器能够在
InnoDB表上使用降序索引。与Using index一起显示。有关更多信息,请参阅 第 10.3.13 节,"降序索引"。Child of '*table*' pushed join@1(JSON:message文本)此表在可以下推到 NDB 内核的连接中被引用为 table 的子项。仅适用于启用了下推连接的 NDB Cluster。有关更多信息和示例,请参阅
ndb_join_pushdown服务器系统变量的描述。const row not found(JSON 属性:const_row_not_found)对于诸如
SELECT ... FROM *tbl_name*之类的查询,表为空。Deleting all rows(JSON 属性:message)对于
DELETE,某些存储引擎(如MyISAM)支持一种以简单快速的方式删除所有表行的处理方法。如果引擎使用此优化,则显示此Extra值。Distinct(JSON 属性:distinct)MySQL 正在查找不同的值,因此在找到第一个匹配行后,它会停止搜索当前行组合的更多行。
FirstMatch(*tbl_name*)(JSON 属性:first_match)半连接 FirstMatch 连接快捷策略用于 tbl_name。
Full scan on NULL key(JSON 属性:message)这在子查询优化中作为后备策略出现,当优化器无法使用索引查找访问方法时。
Impossible HAVING(JSON 属性:message)HAVING子句始终为 false,无法选择任何行。Impossible WHERE(JSON 属性:message)WHERE子句始终为 false,无法选择任何行。Impossible WHERE noticed after reading const tables(JSON 属性:message)LooseScan(*m*..*n*)(JSON 属性:message)使用半连接 LooseScan 策略。m 和 n 是键部分编号。
No matching min/max row(JSON 属性:message)没有行满足诸如
SELECT MIN(...) FROM ... WHERE *condition*之类的查询的条件。no matching row in const table(JSON 属性:message)对于带有连接的查询,存在空表或没有行满足唯一索引条件的表。
No matching rows after partition pruning(JSON 属性:message)对于
DELETE或UPDATE,优化器在分区裁剪后发现没有要删除或更新的内容。它类似于SELECT语句的Impossible WHERE的含义。No tables used(JSON 属性:message)查询没有
FROM子句,或有FROM DUAL子句。对于
INSERT或REPLACE语句,当没有SELECT部分时,EXPLAIN显示此值。例如,它出现在EXPLAIN INSERT INTO t VALUES(10)中,因为这等同于EXPLAIN INSERT INTO t SELECT 10 FROM DUAL。Not exists(JSON 属性:message)MySQL 能够对查询执行
LEFT JOIN优化,并且在找到符合LEFT JOIN条件的一行后,不会为此前的行组合检查此表中的更多行。以下是可以以此方式优化的查询类型示例:SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;假设
t2.id定义为NOT NULL。在这种情况下,MySQL 扫描t1并使用t1.id的值查找t2中的行。如果 MySQL 在t2中找到匹配行,它知道t2.id永远不能为NULL,并且不会扫描t2中具有相同id值的其余行。换句话说,对于t1中的每一行,MySQL 只需要在t2中进行一次查找,无论t2中实际有多少行匹配。这也可以表明形式为
NOT IN (*subquery*)或NOT EXISTS (*subquery*)的WHERE条件已在内部转换为反连接。这会删除子查询并将其表带入最顶层查询的计划中,提供更好的成本规划。通过合并半连接和反连接,优化器可以更自由地重新排序执行计划中的表,在某些情况下产生更快的计划。你可以通过检查执行
EXPLAIN后的SHOW WARNINGS的Message列,或在EXPLAIN FORMAT=TREE的输出中,查看何时对给定查询执行反连接转换。注意
反连接是半连接 table_a JOIN table_b ON condition 的补集。反连接返回 table_a 中的所有行,其中 table_b 中没有行匹配 condition。
Plan is not ready yet(JSON 属性: 无)此值与
EXPLAIN FOR CONNECTION一起出现,当优化器尚未完成为在命名连接中执行的语句创建执行计划时。如果执行计划输出包含多行,则其中任何或所有行都可能具有此Extra值,具体取决于优化器在确定完整执行计划方面的进度。Range checked for each record (index map: *N*)(JSON 属性:message)MySQL 没有找到好的索引可用,但发现在知道前一个表中的列值后可能会使用某些索引。对于前一个表中的每个行组合,MySQL 检查是否可以使用
range或index_merge访问方法来检索行。这不是很快,但比执行没有索引的连接要快。适用性标准如 第 10.2.1.2 节,"范围优化" 和 第 10.2.1.3 节,"索引合并优化" 中所述,例外情况是前一个表的所有列值都是已知的并被视为常量。索引从 1 开始编号,顺序与表的
SHOW INDEX所示相同。索引映射值 N 是一个位掩码值,指示哪些索引是候选项。例如,值0x19(二进制 11001)表示考虑索引 1、4 和 5。Recursive(JSON 属性:recursive)这表明该行适用于递归公用表表达式的递归
SELECT部分。请参阅 第 15.2.20 节,"WITH(公用表表达式)"。Rematerialize(JSON 属性:rematerialize)Rematerialize (X,...)显示在表T的EXPLAIN行中,其中X是任何横向派生表,当读取T的新行时会触发其重新物化。例如:SELECT ... FROM t, LATERAL (derived table that refers to t) AS dt ...每次顶层查询处理
t的新行时,都会重新物化派生表的内容以使其保持最新。Scanned *N* databases(JSON 属性:message)这表明服务器在处理
INFORMATION_SCHEMA表的查询时执行了多少次目录扫描,如 第 10.2.3 节,"优化 INFORMATION_SCHEMA 查询" 中所述。N 的值可以是 0、1 或all。Select tables optimized away(JSON 属性:message)优化器确定 1) 最多应返回一行,以及 2) 要生成此行,必须读取一组确定的行。当可以在优化阶段读取要读取的行时(例如,通过读取索引行),在执行查询期间无需读取任何表。
当查询隐式分组(包含聚合函数但没有
GROUP BY子句)时,满足第一个条件。当每个使用的索引执行一次行查找时,满足第二个条件。读取的索引数量决定了要读取的行数。考虑以下隐式分组查询:
SELECT MIN(c1), MIN(c2) FROM t1;假设可以通过读取一个索引行检索
MIN(c1),并且可以通过从不同索引读取一行来检索MIN(c2)。也就是说,对于每列c1和c2,存在一个索引,其中该列是索引的第一列。在这种情况下,返回一行,由读取两个确定性行生成。如果要读取的行不确定,则不会出现此
Extra值。考虑此查询:SELECT MIN(c2) FROM t1 WHERE c1 <= 10;假设
(c1, c2)是覆盖索引。使用此索引,必须扫描所有c1 <= 10的行以找到最小c2值。相比之下,考虑此查询:SELECT MIN(c2) FROM t1 WHERE c1 = 10;在这种情况下,
c1 = 10的第一个索引行包含最小c2值。只需读取一行即可生成返回的行。对于维护每个表的精确行数的存储引擎(如
MyISAM,但不是InnoDB),此Extra值可以出现在COUNT(*)查询中,其中WHERE子句缺失或始终为 true 且没有GROUP BY子句。(这是隐式分组查询的一个实例,其中存储引擎影响是否可以读取确定数量的行。)Skip_open_table,Open_frm_only,Open_full_table(JSON 属性:message)这些值指示适用于
INFORMATION_SCHEMA表查询的文件打开优化。Skip_open_table: 不需要打开表文件。信息已从数据字典中获得。Open_frm_only: 只需读取数据字典即可获取表信息。Open_full_table: 未优化的信息查找。必须从数据字典读取表信息并通过读取表文件。
Start temporary,End temporary(JSON 属性:message)这表明半连接 Duplicate Weedout 策略使用临时表。
unique row not found(JSON 属性:message)对于诸如
SELECT ... FROM *tbl_name*之类的查询,没有行满足表上UNIQUE索引或PRIMARY KEY的条件。Using filesort(JSON 属性:using_filesort)MySQL 必须进行额外的传递以找出如何按排序顺序检索行。排序是通过根据连接类型遍历所有行并为所有匹配
WHERE子句的行存储排序键和指向行的指针来完成的。然后对键进行排序并按排序顺序检索行。请参阅 第 10.2.1.16 节,"ORDER BY 优化"。Using index(JSON 属性:using_index)列信息仅使用索引树中的信息从表中检索,而无需进行额外的查找来读取实际行。当查询仅使用属于单个索引的列时,可以使用此策略。
对于具有用户定义聚簇索引的
InnoDB表,即使Extra列中缺少Using index,也可以使用。如果type是index且key是PRIMARY,就是这种情况。EXPLAIN FORMAT=TRADITIONAL和EXPLAIN FORMAT=JSON会显示有关使用的任何覆盖索引的信息。EXPLAIN FORMAT=TREE也会显示。Using index condition(JSON 属性:using_index_condition)通过访问索引元组并首先测试它们来确定是否读取完整表行来读取表。通过这种方式,索引信息用于延迟(“下推”)读取完整表行,除非必要。请参阅 第 10.2.1.6 节,"索引条件下推优化"。
Using index for group-by(JSON 属性:using_index_for_group_by)与
Using index表访问方法类似,Using index for group-by表示 MySQL 找到了一个索引,可用于检索GROUP BY或DISTINCT查询的所有列,而无需对实际表进行额外的磁盘访问。此外,索引以最有效的方式使用,因此对于每个组,只读取少量索引条目。有关详细信息,请参阅 第 10.2.1.17 节,"GROUP BY 优化"。Using index for skip scan(JSON 属性:using_index_for_skip_scan)表示使用了 Skip Scan 访问方法。请参阅 Skip Scan 范围访问方法。
Using join buffer (Block Nested Loop),Using join buffer (Batched Key Access),Using join buffer (hash join)(JSON 属性:using_join_buffer)来自早期连接的表被分批读入连接缓冲区,然后从缓冲区中使用它们的行与当前表执行连接。
(Block Nested Loop)表示使用 Block Nested-Loop 算法,(Batched Key Access)表示使用 Batched Key Access 算法,(hash join)表示使用哈希连接。也就是说,来自EXPLAIN输出上一行的表中的键被缓冲,并且从Using join buffer出现的行所代表的表中批量获取匹配的行。在 JSON 格式的输出中,
using_join_buffer的值始终是Block Nested Loop、Batched Key Access或hash join之一。有关哈希连接的更多信息,请参阅 第 10.2.1.4 节,"哈希连接优化"。
有关 Batched Key Access 算法的信息,请参阅 Batched Key Access 连接。
Using MRR(JSON 属性:message)使用多范围读取优化策略读取表。请参阅 第 10.2.1.11 节,"多范围读取优化"。
Using sort_union(...),Using union(...),Using intersect(...)(JSON 属性:message)这些指示显示如何为
index_merge连接类型合并索引扫描的特定算法。请参阅 第 10.2.1.3 节,"索引合并优化"。Using temporary(JSON 属性:using_temporary_table)为了解析查询,MySQL 需要创建临时表来保存结果。如果查询包含以不同方式列出列的
GROUP BY和ORDER BY子句,通常会发生这种情况。Using where(JSON 属性:attached_condition)使用
WHERE子句来限制要与下一个表匹配或发送到客户端的行。除非你特意打算从表中获取或检查所有行,否则如果Extra值不是Using where且表连接类型是ALL或index,则你的查询可能有问题。Using where在 JSON 格式的输出中没有直接对应项;attached_condition属性包含使用的任何WHERE条件。Using where with pushed condition(JSON 属性:message)此项仅适用于
NDB表。这意味着 NDB Cluster 正在使用条件下推优化来提高非索引列与常量之间直接比较的效率。在这种情况下,条件被“下推”到集群的数据节点,并在所有数据节点上同时评估。这消除了通过网络发送不匹配行的需要,并且可以将此类查询的速度提高 5 到 10 倍,超过可以使用但未使用条件下推的情况。有关更多信息,请参阅 第 10.2.1.5 节,"引擎条件下推优化"。Zero limit(JSON 属性:message)查询有
LIMIT 0子句,无法选择任何行。
EXPLAIN 输出解释
通过取 EXPLAIN 输出的 rows 列中值的乘积,你可以很好地了解连接的质量。这应该告诉你 MySQL 必须检查多少行才能执行查询。如果你使用 max_join_size 系统变量限制查询,则此行乘积还用于确定执行哪些多表 SELECT 语句以及中止哪些语句。请参阅 第 7.1.1 节,"配置服务器"。
以下示例显示如何根据 EXPLAIN 提供的信息逐步优化多表连接。
假设你有此处显示的 SELECT 语句,并且计划使用 EXPLAIN 检查它:
EXPLAIN SELECT tt.TicketNumber, tt.TimeIn,
tt.ProjectReference, tt.EstimatedShipDate,
tt.ActualShipDate, tt.ClientID,
tt.ServiceCodes, tt.RepetitiveID,
tt.CurrentProcess, tt.CurrentDPPerson,
tt.RecordVolume, tt.DPPrinted, et.COUNTRY,
et_1.COUNTRY, do.CUSTNAME
FROM tt, et, et AS et_1, do
WHERE tt.SubmitTime IS NULL
AND tt.ActualPC = et.EMPLOYID
AND tt.AssignedPC = et_1.EMPLOYID
AND tt.ClientID = do.CUSTNMBR;
对于此示例,做出以下假设:
正在比较的列声明如下。
表 列 数据类型 ttActualPCCHAR(10)ttAssignedPCCHAR(10)ttClientIDCHAR(10)etEMPLOYIDCHAR(15)doCUSTNMBRCHAR(15)表具有以下索引。
表 索引 ttActualPCttAssignedPCttClientIDetEMPLOYID(主键)doCUSTNMBR(主键)tt.ActualPC值分布不均匀。
最初,在执行任何优化之前,EXPLAIN 语句生成以下信息:
table type possible_keys key key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
do ALL PRIMARY NULL NULL NULL 2135
et_1 ALL PRIMARY NULL NULL NULL 74
tt ALL AssignedPC, NULL NULL NULL 3872
ClientID,
ActualPC
Range checked for each record (index map: 0x23)
因为每个表的 type 都是 ALL,所以此输出表明 MySQL 正在生成所有表的笛卡尔积;即,每行的组合。这需要相当长的时间,因为必须检查每个表中行数的乘积。对于手头的情况,此乘积为 74 × 2135 × 74 × 3872 = 45,268,558,720 行。如果表更大,你可以想象需要多长时间。
这里的一个问题是,如果将列声明为相同的类型和大小,MySQL 可以更有效地使用列上的索引。在这种情况下,如果 VARCHAR 和 CHAR 声明为相同的大小,则被视为相同。tt.ActualPC 声明为 CHAR(10),et.EMPLOYID 为 CHAR(15),因此存在长度不匹配。
要修复列长度之间的这种差异,请使用 ALTER TABLE 将 ActualPC 从 10 个字符延长到 15 个字符:
mysql> ALTER TABLE tt MODIFY ActualPC VARCHAR(15);
现在 tt.ActualPC 和 et.EMPLOYID 都是 VARCHAR(15)。再次执行 EXPLAIN 语句会产生此结果:
table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC, NULL NULL NULL 3872 Using
ClientID, where
ActualPC
do ALL PRIMARY NULL NULL NULL 2135
Range checked for each record (index map: 0x1)
et_1 ALL PRIMARY NULL NULL NULL 74
Range checked for each record (index map: 0x1)
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
这并不完美,但要好得多:rows 值的乘积减少了 74 倍。此版本在几秒钟内执行。
可以进行第二次更改以消除 tt.AssignedPC = et_1.EMPLOYID 和 tt.ClientID = do.CUSTNMBR 比较的列长度不匹配:
mysql> ALTER TABLE tt MODIFY AssignedPC VARCHAR(15),
MODIFY ClientID VARCHAR(15);
在该修改之后,EXPLAIN 产生此处显示的输出:
table type possible_keys key key_len ref rows Extra
et ALL PRIMARY NULL NULL NULL 74
tt ref AssignedPC, ActualPC 15 et.EMPLOYID 52 Using
ClientID, where
ActualPC
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
此时,查询几乎已经优化到最好。剩下的问题是,默认情况下,MySQL 假设 tt.ActualPC 列中的值均匀分布,而 tt 表并非如此。幸运的是,告诉 MySQL 分析键分布很容易:
mysql> ANALYZE TABLE tt;
有了额外的索引信息,连接就完美了,EXPLAIN 产生了这个结果:
table type possible_keys key key_len ref rows Extra
tt ALL AssignedPC NULL NULL NULL 3872 Using
ClientID, where
ActualPC
et eq_ref PRIMARY PRIMARY 15 tt.ActualPC 1
et_1 eq_ref PRIMARY PRIMARY 15 tt.AssignedPC 1
do eq_ref PRIMARY PRIMARY 15 tt.ClientID 1
EXPLAIN 输出中的 rows 列是 MySQL 连接优化器的有根据的猜测。通过将 rows 乘积与查询返回的实际行数进行比较,检查这些数字是否接近真实情况。如果数字相差很大,你可能会通过在 SELECT 语句中使用 STRAIGHT_JOIN 并尝试以不同的顺序列出 FROM 子句中的表来获得更好的性能。(但是,STRAIGHT_JOIN 可能会阻止使用索引,因为它禁用了半连接转换。请参阅 使用半连接转换优化 IN 和 EXISTS 子查询谓词。)