11[#explain-in-detail]
2- == MySQL `explain` 详解
2+ = MySQL `explain` 详解
33include::_attributes.adoc[]
44
55进行 MySQL 查询优化,`explain` 是必备技能。这章就来重点介绍一下 `explain` 。
66
77image::assets/images/sql-joins.png[title="SQL Joins", alt="SQL Joins", width="95%"]
88
9- === 示例数据库
9+ == 示例数据库
1010
1111为了方便后续讲解,这里使用 MySQL 官方提供的示例数据库: https://dev.mysql.com/doc/sakila/en/[MySQL : Sakila Sample Database^]。需求的小伙伴,请到官方页面下载并安装。
1212
@@ -21,7 +21,7 @@ image::assets/images/sakila-schema-detail-2.png[title="Sakila Sample Database",
2121
2222
2323[[explain-syntax]]
24- === `EXPLAIN` 语法
24+ == `EXPLAIN` 语法
2525
2626`DESCRIBE` 和 `EXPLAIN` 是同义词。在实践中,`DESCRIBE` 多用于显示表结构,而 `EXPLAIN` 多用于显示 SQL 语句的执行计划。
2727
@@ -64,7 +64,7 @@ FROM actor
6464WHERE actor_id = 1;
6565----
6666
67- === `DESCRIBE` 获取表结构
67+ == `DESCRIBE` 获取表结构
6868
6969`DESCRIBE` 是 `SHOW COLUMNS` 的简写形式。
7070
@@ -81,7 +81,7 @@ mysql> DESCRIBE actor;
8181+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
8282----
8383
84- === `SHOW PROFILES` 显示执行时间
84+ == `SHOW PROFILES` 显示执行时间
8585
8686在 MySQL 数据库中,可以通过配置 `profiling` 参数来启用 SQL 剖析。
8787
@@ -124,54 +124,54 @@ SHOW PROFILE SWAPS FOR QUERY 119;
124124
125125
126126[[explain-output]]
127- === `EXPLAIN` 输出
127+ == `EXPLAIN` 输出
128128
129- ==== `id`
129+ === `id`
130130
131131`SELECT` 标识符,SQL 执行的顺序的标识,SQL 从大到小的执行。如果在语句中没子查询或关联查询,只有唯一的 `SELECT`,每行都将显示 `1`。否则,内层的 `SELECT` 语句一般会顺序编号,对应于其在原始语句中的位置
132132
133133* `id` 相同时,执行顺序由上至下
134134* 如果是子查询,`id` 的序号会递增,`id` 值越大优先级越高,越先被执行
135135* 如果 `id` 相同,则认为是一组,从上往下顺序执行;在所有组中,`id` 值越大,优先级越高,越先执行
136136
137- ==== `select_type`
137+ === `select_type`
138138
139139
140- ===== `SIMPLE`
140+ ==== `SIMPLE`
141141简单 `SELECT`,不使用 `UNION` 或子查询等
142142
143- ===== `PRIMARY`
143+ ==== `PRIMARY`
144144查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY
145145
146- ===== `UNION`
146+ ==== `UNION`
147147UNION中的第二个或后面的SELECT语句
148148
149- ===== `DEPENDENT UNION`
149+ ==== `DEPENDENT UNION`
150150UNION中的第二个或后面的SELECT语句,取决于外面的查询
151151
152- ===== `UNION RESULT`
152+ ==== `UNION RESULT`
153153UNION的结果
154154
155- ===== `SUBQUERY`
155+ ==== `SUBQUERY`
156156子查询中的第一个SELECT
157157
158- ===== `DEPENDENT SUBQUERY`
158+ ==== `DEPENDENT SUBQUERY`
159159子查询中的第一个SELECT,取决于外面的查询
160160
161- ===== `DERIVED`
161+ ==== `DERIVED`
162162派生表的SELECT, FROM子句的子查询
163163
164- ===== `DEPENDENT DERIVED`
164+ ==== `DEPENDENT DERIVED`
165165派生表的SELECT, FROM子句的子查询
166166`MATERIALIZED`::
167167
168- ===== `UNCACHEABLE SUBQUERY`
168+ ==== `UNCACHEABLE SUBQUERY`
169169一个子查询的结果不能被缓存,必须重新评估外链接的第一行
170170
171- ===== `UNCACHEABLE UNION`
171+ ==== `UNCACHEABLE UNION`
172172??
173173
174- ==== `table`
174+ === `table`
175175
176176访问引用哪个表(例如下面的 `actor`):
177177
@@ -183,17 +183,17 @@ FROM actor
183183WHERE actor_id = 1;
184184----
185185
186- ==== `partitions`
186+ === `partitions`
187187
188- ==== `type`
188+ === `type`
189189
190190`type` 显示的是数据访问类型,是较为重要的一个指标,结果值从好到坏依次是:
191191`system` > `const` > `eq_ref` > `ref` > `fulltext` > `ref_or_null` > `index_merge` > `unique_subquery` > `index_subquery` > `range` > `index` > `ALL`。一般来说,得保证查询至少达到 `range` 级别,最好能达到 `ref`。
192192
193- ===== `system`
193+ ==== `system`
194194当 MySQL 对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于 `WHERE` 列表中,MySQL 就能将该查询转换为一个常量。`system` 是 `const` 类型的特例,当查询的表只有一行的情况下,使用 `system`。
195195
196- ===== `const`
196+ ==== `const`
197197
198198在查询开始时读取,该表最多有一个匹配行。因为只有一行,所以这一行中的列的值可以被其他优化器视为常量。`const` 表非常快,因为它们只读取一次。
199199
@@ -205,14 +205,14 @@ FROM actor
205205WHERE actor_id = 1;
206206----
207207
208- ===== `eq_ref`
208+ ==== `eq_ref`
209209
210210类似 `ref`,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用 `PRIMARY KEY` 或者 `UNIQUE KEY` 作为关联条件
211211
212212最多只返回一条符合条件的记录。使用唯一性索引或主键查找时会发生(高效)。
213213
214214
215- ===== `ref`
215+ ==== `ref`
216216
217217表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
218218
@@ -226,7 +226,7 @@ FROM address
226226WHERE city_id = 119;
227227----
228228
229- ===== `fulltext`
229+ ==== `fulltext`
230230
231231全文检索
232232
@@ -239,17 +239,17 @@ WHERE MATCH(title, description) AGAINST('ACE')
239239LIMIT 100;
240240----
241241
242- ===== `ref_or_null`
242+ ==== `ref_or_null`
243243
244244MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
245245
246- ===== `index_merge`
246+ ==== `index_merge`
247247
248- ===== `unique_subquery`
248+ ==== `unique_subquery`
249249
250- ===== `index_subquery`
250+ ==== `index_subquery`
251251
252- ===== `range`
252+ ==== `range`
253253
254254范围扫描,一个有限制的索引扫描。`key` 列显示使用了哪个索引。当使用 `=`、 `<>`、`>`、`>=`、`<`、`<=`、`IS NULL`、`<=>`、`BETWEEN` 或者 `IN` 操作符,用常量比较关键字列时,可以使用 `range`。
255255
@@ -261,11 +261,11 @@ FROM actor
261261WHERE actor_id > 100;
262262----
263263
264- ===== `index`
264+ ==== `index`
265265
266266Full Index Scan,`index` 与 `ALL` 区别为 `index` 类型只遍历索引树。和全表扫描一样。只是扫描表的时候按照索引次序进行而不是行。主要优点就是避免了排序, 但是开销仍然非常大。如在 `Extra` 列看到 `Using index`,说明正在使用覆盖索引,只扫描索引的数据,它比按索引次序全表扫描的开销要小很多
267267
268- ===== `ALL`
268+ ==== `ALL`
269269
270270Full Table Scan,最坏的情况,全表扫描,MySQL 将遍历全表以找到匹配的行。
271271
@@ -276,51 +276,51 @@ SELECT *
276276FROM actor;
277277----
278278
279- ==== `possible_keys`
279+ === `possible_keys`
280280
281281显示查询使用了哪些索引,表示该索引可以进行高效地查找,但是列出来的索引对于后续优化过程可能是没有用的。
282282
283- ==== `key`
283+ === `key`
284284
285285`key` 列显示 MySQL 实际决定使用的键(索引)。如果没有选择索引,键是 `NULL`。要想强制 MySQL 使用或忽视 `possible_keys` 列中的索引,在查询中使用 `FORCE INDEX`、`USE INDEX` 或者 `IGNORE INDEX`。
286286
287- ==== `key_len`
287+ === `key_len`
288288
289289`key_len` 列显示 MySQL 决定使用的键长度。如果键是 `NULL`,则长度为 `NULL`。使用的索引的长度。在不损失精确性的情况下,长度越短越好 。
290290
291- ==== `ref`
291+ === `ref`
292292
293293`ref` 列显示使用哪个列或常数与 `key` 一起从表中选择行。
294294
295- ==== `rows`
295+ === `rows`
296296
297297`rows` 列显示 MySQL 认为它执行查询时必须检查的行数。注意这是一个预估值。
298298
299- ==== `filtered`
299+ === `filtered`
300300
301301给出了一个百分比的值,这个百分比值和 rows 列的值一起使用。(5.7才有)
302302
303- ==== `Extra`
303+ === `Extra`
304304
305305`Extra` 是 `EXPLAIN` 输出中另外一个很重要的列,该列显示 MySQL 在查询过程中的一些详细信息,MySQL 查询优化器执行查询的过程中对查询计划的重要补充信息。
306306
307- ===== `Child of *'table'* pushed join@1`
307+ ==== `Child of *'table'* pushed join@1`
308308
309- ===== `const row not found`
309+ ==== `const row not found`
310310
311- ===== `Deleting all rows`
311+ ==== `Deleting all rows`
312312
313- ===== `Distinct`
313+ ==== `Distinct`
314314
315315优化 `DISTINCT` 操作,在找到第一匹配的元组后即停止找同样值的动作
316316
317- ===== `FirstMatch(**tbl_name**)`
317+ ==== `FirstMatch(**tbl_name**)`
318318
319- ===== `Full scan on NULL key`
319+ ==== `Full scan on NULL key`
320320
321- ===== `Impossible HAVING`
321+ ==== `Impossible HAVING`
322322
323- ===== `Impossible WHERE`
323+ ==== `Impossible WHERE`
324324
325325[{sql_source_attr}]
326326----
@@ -333,28 +333,28 @@ WHERE actor_id IS NULL;
333333因为 `actor_id` 是 `actor` 表的主键。所以,这个条件不可能成立。
334334
335335
336- ===== `Impossible WHERE noticed after reading const tables`
336+ ==== `Impossible WHERE noticed after reading const tables`
337337
338- ===== `LooseScan(**m..n**)`
338+ ==== `LooseScan(**m..n**)`
339339
340- ===== `No matching min/max row`
340+ ==== `No matching min/max row`
341341
342- ===== `no matching row in const table`
342+ ==== `no matching row in const table`
343343
344- ===== `No matching rows after partition pruning`
344+ ==== `No matching rows after partition pruning`
345345
346- ===== `No tables used`
346+ ==== `No tables used`
347347
348- ===== `Not exists`
348+ ==== `Not exists`
349349
350350MySQL 优化了 `LEFT JOIN`,一旦它找到了匹配 `LEFT JOIN` 标准的行, 就不再搜索了。
351351
352- ===== `Plan isn't ready yet`
353- ===== `Range checked for each record (index map: **N**)`
354- ===== `Recursive`
355- ===== `Rematerialize`
356- ===== `Scanned *N* databases`
357- ===== `Select tables optimized away`
352+ ==== `Plan isn't ready yet`
353+ ==== `Range checked for each record (index map: **N**)`
354+ ==== `Recursive`
355+ ==== `Rematerialize`
356+ ==== `Scanned *N* databases`
357+ ==== `Select tables optimized away`
358358
359359在没有 `GROUP BY` 子句的情况下,基于索引优化 `MIN/MAX` 操作,或者对于 MyISAM 存储引擎优化 `COUNT(*)` 操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
360360
@@ -365,39 +365,39 @@ SELECT MIN(actor_id), MAX(actor_id)
365365FROM actor;
366366----
367367
368- ===== `Skip_open_table, Open_frm_only, Open_full_table`
368+ ==== `Skip_open_table, Open_frm_only, Open_full_table`
369369
370370* `Skip_open_table`
371371* `Open_frm_only`
372372* `Open_full_table`
373373
374- ===== `Start temporary, End temporary`
375- ===== `unique row not found`
376- ===== `Using filesort`
374+ ==== `Start temporary, End temporary`
375+ ==== `unique row not found`
376+ ==== `Using filesort`
377377
378378MySQL 有两种方式可以生成有序的结果,通过排序操作或者使用索引,当 `Extra` 中出现了 `Using filesort` 说明MySQL使用了后者,但注意虽然叫 `filesort` 但并不是说明就是用了文件来进行排序,只要可能排序都是在内存里完成的。大部分情况下利用索引排序更快,所以一般这时也要考虑优化查询了。使用文件完成排序操作,这是可能是 `ordery by`,`group by` 语句的结果,这可能是一个 CPU 密集型的过程,可以通过选择合适的索引来改进性能,用索引来为查询结果排序。
379379
380- ===== `Using index`
380+ ==== `Using index`
381381
382382说明查询是覆盖了索引的,不需要读取数据文件,从索引树(索引文件)中即可获得信息。如果同时出现 `using where`,表明索引被用来执行索引键值的查找,没有 `using where`,表明索引用来读取数据而非执行查找动作。这是MySQL 服务层完成的,但无需再回表查询记录。
383383
384- ===== `Using index condition`
384+ ==== `Using index condition`
385385
386386这是 MySQL 5.6 出来的新特性,叫做“索引条件推送”。简单说一点就是 MySQL 原来在索引上是不能执行如 `like` 这样的操作的,但是现在可以了,这样减少了不必要的 I/O 操作,但是只能用在二级索引上。
387387
388- ===== `Using index for group-by`
389- ===== `Using index for skip scan`
390- ===== `Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access)`
388+ ==== `Using index for group-by`
389+ ==== `Using index for skip scan`
390+ ==== `Using join buffer (Block Nested Loop), Using join buffer (Batched Key Access)`
391391
392392使用了连接缓存:Block Nested Loop,连接算法是块嵌套循环连接;Batched Key Access,连接算法是批量索引连接。
393393
394- ===== `Using MRR`
395- ===== `Using sort_union(...), Using union(...), Using intersect(...)`
396- ===== `Using temporary`
394+ ==== `Using MRR`
395+ ==== `Using sort_union(...), Using union(...), Using intersect(...)`
396+ ==== `Using temporary`
397397
398398用临时表保存中间结果,常用于 `GROUP BY` 和 `ORDER BY` 操作中,一般看到它说明查询需要优化了,就算避免不了临时表的使用也要尽量避免硬盘临时表的使用。
399399
400- ===== `Using where`
400+ ==== `Using where`
401401
402402使用了 `WHERE` 从句来限制哪些行将与下一张表匹配或者是返回给用户。注意:`Extra` 列出现 `Using where` 表示MySQL 服务器将存储引擎返回服务层以后再应用 `WHERE` 条件过滤。
403403
@@ -409,8 +409,8 @@ FROM actor
409409WHERE actor_id > 100;
410410----
411411
412- ===== `Using where with pushed condition`
413- ===== `Zero limit`
412+ ==== `Using where with pushed condition`
413+ ==== `Zero limit`
414414
415415查询有 `LIMIT 0` 子句,所以导致不能选出任何一行。
416416
0 commit comments