1
1
[#explain-in-detail]
2
- == MySQL `explain` 详解
2
+ = MySQL `explain` 详解
3
3
include::_attributes.adoc[]
4
4
5
5
进行 MySQL 查询优化,`explain` 是必备技能。这章就来重点介绍一下 `explain` 。
6
6
7
7
image::assets/images/sql-joins.png[title="SQL Joins", alt="SQL Joins", width="95%"]
8
8
9
- === 示例数据库
9
+ == 示例数据库
10
10
11
11
为了方便后续讲解,这里使用 MySQL 官方提供的示例数据库: https://dev.mysql.com/doc/sakila/en/[MySQL : Sakila Sample Database^]。需求的小伙伴,请到官方页面下载并安装。
12
12
@@ -21,7 +21,7 @@ image::assets/images/sakila-schema-detail-2.png[title="Sakila Sample Database",
21
21
22
22
23
23
[[explain-syntax]]
24
- === `EXPLAIN` 语法
24
+ == `EXPLAIN` 语法
25
25
26
26
`DESCRIBE` 和 `EXPLAIN` 是同义词。在实践中,`DESCRIBE` 多用于显示表结构,而 `EXPLAIN` 多用于显示 SQL 语句的执行计划。
27
27
@@ -64,7 +64,7 @@ FROM actor
64
64
WHERE actor_id = 1;
65
65
----
66
66
67
- === `DESCRIBE` 获取表结构
67
+ == `DESCRIBE` 获取表结构
68
68
69
69
`DESCRIBE` 是 `SHOW COLUMNS` 的简写形式。
70
70
@@ -81,7 +81,7 @@ mysql> DESCRIBE actor;
81
81
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
82
82
----
83
83
84
- === `SHOW PROFILES` 显示执行时间
84
+ == `SHOW PROFILES` 显示执行时间
85
85
86
86
在 MySQL 数据库中,可以通过配置 `profiling` 参数来启用 SQL 剖析。
87
87
@@ -124,54 +124,54 @@ SHOW PROFILE SWAPS FOR QUERY 119;
124
124
125
125
126
126
[[explain-output]]
127
- === `EXPLAIN` 输出
127
+ == `EXPLAIN` 输出
128
128
129
- ==== `id`
129
+ === `id`
130
130
131
131
`SELECT` 标识符,SQL 执行的顺序的标识,SQL 从大到小的执行。如果在语句中没子查询或关联查询,只有唯一的 `SELECT`,每行都将显示 `1`。否则,内层的 `SELECT` 语句一般会顺序编号,对应于其在原始语句中的位置
132
132
133
133
* `id` 相同时,执行顺序由上至下
134
134
* 如果是子查询,`id` 的序号会递增,`id` 值越大优先级越高,越先被执行
135
135
* 如果 `id` 相同,则认为是一组,从上往下顺序执行;在所有组中,`id` 值越大,优先级越高,越先执行
136
136
137
- ==== `select_type`
137
+ === `select_type`
138
138
139
139
140
- ===== `SIMPLE`
140
+ ==== `SIMPLE`
141
141
简单 `SELECT`,不使用 `UNION` 或子查询等
142
142
143
- ===== `PRIMARY`
143
+ ==== `PRIMARY`
144
144
查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY
145
145
146
- ===== `UNION`
146
+ ==== `UNION`
147
147
UNION中的第二个或后面的SELECT语句
148
148
149
- ===== `DEPENDENT UNION`
149
+ ==== `DEPENDENT UNION`
150
150
UNION中的第二个或后面的SELECT语句,取决于外面的查询
151
151
152
- ===== `UNION RESULT`
152
+ ==== `UNION RESULT`
153
153
UNION的结果
154
154
155
- ===== `SUBQUERY`
155
+ ==== `SUBQUERY`
156
156
子查询中的第一个SELECT
157
157
158
- ===== `DEPENDENT SUBQUERY`
158
+ ==== `DEPENDENT SUBQUERY`
159
159
子查询中的第一个SELECT,取决于外面的查询
160
160
161
- ===== `DERIVED`
161
+ ==== `DERIVED`
162
162
派生表的SELECT, FROM子句的子查询
163
163
164
- ===== `DEPENDENT DERIVED`
164
+ ==== `DEPENDENT DERIVED`
165
165
派生表的SELECT, FROM子句的子查询
166
166
`MATERIALIZED`::
167
167
168
- ===== `UNCACHEABLE SUBQUERY`
168
+ ==== `UNCACHEABLE SUBQUERY`
169
169
一个子查询的结果不能被缓存,必须重新评估外链接的第一行
170
170
171
- ===== `UNCACHEABLE UNION`
171
+ ==== `UNCACHEABLE UNION`
172
172
??
173
173
174
- ==== `table`
174
+ === `table`
175
175
176
176
访问引用哪个表(例如下面的 `actor`):
177
177
@@ -183,17 +183,17 @@ FROM actor
183
183
WHERE actor_id = 1;
184
184
----
185
185
186
- ==== `partitions`
186
+ === `partitions`
187
187
188
- ==== `type`
188
+ === `type`
189
189
190
190
`type` 显示的是数据访问类型,是较为重要的一个指标,结果值从好到坏依次是:
191
191
`system` > `const` > `eq_ref` > `ref` > `fulltext` > `ref_or_null` > `index_merge` > `unique_subquery` > `index_subquery` > `range` > `index` > `ALL`。一般来说,得保证查询至少达到 `range` 级别,最好能达到 `ref`。
192
192
193
- ===== `system`
193
+ ==== `system`
194
194
当 MySQL 对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于 `WHERE` 列表中,MySQL 就能将该查询转换为一个常量。`system` 是 `const` 类型的特例,当查询的表只有一行的情况下,使用 `system`。
195
195
196
- ===== `const`
196
+ ==== `const`
197
197
198
198
在查询开始时读取,该表最多有一个匹配行。因为只有一行,所以这一行中的列的值可以被其他优化器视为常量。`const` 表非常快,因为它们只读取一次。
199
199
@@ -205,14 +205,14 @@ FROM actor
205
205
WHERE actor_id = 1;
206
206
----
207
207
208
- ===== `eq_ref`
208
+ ==== `eq_ref`
209
209
210
210
类似 `ref`,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用 `PRIMARY KEY` 或者 `UNIQUE KEY` 作为关联条件
211
211
212
212
最多只返回一条符合条件的记录。使用唯一性索引或主键查找时会发生(高效)。
213
213
214
214
215
- ===== `ref`
215
+ ==== `ref`
216
216
217
217
表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
218
218
@@ -226,7 +226,7 @@ FROM address
226
226
WHERE city_id = 119;
227
227
----
228
228
229
- ===== `fulltext`
229
+ ==== `fulltext`
230
230
231
231
全文检索
232
232
@@ -239,17 +239,17 @@ WHERE MATCH(title, description) AGAINST('ACE')
239
239
LIMIT 100;
240
240
----
241
241
242
- ===== `ref_or_null`
242
+ ==== `ref_or_null`
243
243
244
244
MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
245
245
246
- ===== `index_merge`
246
+ ==== `index_merge`
247
247
248
- ===== `unique_subquery`
248
+ ==== `unique_subquery`
249
249
250
- ===== `index_subquery`
250
+ ==== `index_subquery`
251
251
252
- ===== `range`
252
+ ==== `range`
253
253
254
254
范围扫描,一个有限制的索引扫描。`key` 列显示使用了哪个索引。当使用 `=`、 `<>`、`>`、`>=`、`<`、`<=`、`IS NULL`、`<=>`、`BETWEEN` 或者 `IN` 操作符,用常量比较关键字列时,可以使用 `range`。
255
255
@@ -261,11 +261,11 @@ FROM actor
261
261
WHERE actor_id > 100;
262
262
----
263
263
264
- ===== `index`
264
+ ==== `index`
265
265
266
266
Full Index Scan,`index` 与 `ALL` 区别为 `index` 类型只遍历索引树。和全表扫描一样。只是扫描表的时候按照索引次序进行而不是行。主要优点就是避免了排序, 但是开销仍然非常大。如在 `Extra` 列看到 `Using index`,说明正在使用覆盖索引,只扫描索引的数据,它比按索引次序全表扫描的开销要小很多
267
267
268
- ===== `ALL`
268
+ ==== `ALL`
269
269
270
270
Full Table Scan,最坏的情况,全表扫描,MySQL 将遍历全表以找到匹配的行。
271
271
@@ -276,51 +276,51 @@ SELECT *
276
276
FROM actor;
277
277
----
278
278
279
- ==== `possible_keys`
279
+ === `possible_keys`
280
280
281
281
显示查询使用了哪些索引,表示该索引可以进行高效地查找,但是列出来的索引对于后续优化过程可能是没有用的。
282
282
283
- ==== `key`
283
+ === `key`
284
284
285
285
`key` 列显示 MySQL 实际决定使用的键(索引)。如果没有选择索引,键是 `NULL`。要想强制 MySQL 使用或忽视 `possible_keys` 列中的索引,在查询中使用 `FORCE INDEX`、`USE INDEX` 或者 `IGNORE INDEX`。
286
286
287
- ==== `key_len`
287
+ === `key_len`
288
288
289
289
`key_len` 列显示 MySQL 决定使用的键长度。如果键是 `NULL`,则长度为 `NULL`。使用的索引的长度。在不损失精确性的情况下,长度越短越好 。
290
290
291
- ==== `ref`
291
+ === `ref`
292
292
293
293
`ref` 列显示使用哪个列或常数与 `key` 一起从表中选择行。
294
294
295
- ==== `rows`
295
+ === `rows`
296
296
297
297
`rows` 列显示 MySQL 认为它执行查询时必须检查的行数。注意这是一个预估值。
298
298
299
- ==== `filtered`
299
+ === `filtered`
300
300
301
301
给出了一个百分比的值,这个百分比值和 rows 列的值一起使用。(5.7才有)
302
302
303
- ==== `Extra`
303
+ === `Extra`
304
304
305
305
`Extra` 是 `EXPLAIN` 输出中另外一个很重要的列,该列显示 MySQL 在查询过程中的一些详细信息,MySQL 查询优化器执行查询的过程中对查询计划的重要补充信息。
306
306
307
- ===== `Child of *'table'* pushed join@1`
307
+ ==== `Child of *'table'* pushed join@1`
308
308
309
- ===== `const row not found`
309
+ ==== `const row not found`
310
310
311
- ===== `Deleting all rows`
311
+ ==== `Deleting all rows`
312
312
313
- ===== `Distinct`
313
+ ==== `Distinct`
314
314
315
315
优化 `DISTINCT` 操作,在找到第一匹配的元组后即停止找同样值的动作
316
316
317
- ===== `FirstMatch(**tbl_name**)`
317
+ ==== `FirstMatch(**tbl_name**)`
318
318
319
- ===== `Full scan on NULL key`
319
+ ==== `Full scan on NULL key`
320
320
321
- ===== `Impossible HAVING`
321
+ ==== `Impossible HAVING`
322
322
323
- ===== `Impossible WHERE`
323
+ ==== `Impossible WHERE`
324
324
325
325
[{sql_source_attr}]
326
326
----
@@ -333,28 +333,28 @@ WHERE actor_id IS NULL;
333
333
因为 `actor_id` 是 `actor` 表的主键。所以,这个条件不可能成立。
334
334
335
335
336
- ===== `Impossible WHERE noticed after reading const tables`
336
+ ==== `Impossible WHERE noticed after reading const tables`
337
337
338
- ===== `LooseScan(**m..n**)`
338
+ ==== `LooseScan(**m..n**)`
339
339
340
- ===== `No matching min/max row`
340
+ ==== `No matching min/max row`
341
341
342
- ===== `no matching row in const table`
342
+ ==== `no matching row in const table`
343
343
344
- ===== `No matching rows after partition pruning`
344
+ ==== `No matching rows after partition pruning`
345
345
346
- ===== `No tables used`
346
+ ==== `No tables used`
347
347
348
- ===== `Not exists`
348
+ ==== `Not exists`
349
349
350
350
MySQL 优化了 `LEFT JOIN`,一旦它找到了匹配 `LEFT JOIN` 标准的行, 就不再搜索了。
351
351
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`
358
358
359
359
在没有 `GROUP BY` 子句的情况下,基于索引优化 `MIN/MAX` 操作,或者对于 MyISAM 存储引擎优化 `COUNT(*)` 操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
360
360
@@ -365,39 +365,39 @@ SELECT MIN(actor_id), MAX(actor_id)
365
365
FROM actor;
366
366
----
367
367
368
- ===== `Skip_open_table, Open_frm_only, Open_full_table`
368
+ ==== `Skip_open_table, Open_frm_only, Open_full_table`
369
369
370
370
* `Skip_open_table`
371
371
* `Open_frm_only`
372
372
* `Open_full_table`
373
373
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`
377
377
378
378
MySQL 有两种方式可以生成有序的结果,通过排序操作或者使用索引,当 `Extra` 中出现了 `Using filesort` 说明MySQL使用了后者,但注意虽然叫 `filesort` 但并不是说明就是用了文件来进行排序,只要可能排序都是在内存里完成的。大部分情况下利用索引排序更快,所以一般这时也要考虑优化查询了。使用文件完成排序操作,这是可能是 `ordery by`,`group by` 语句的结果,这可能是一个 CPU 密集型的过程,可以通过选择合适的索引来改进性能,用索引来为查询结果排序。
379
379
380
- ===== `Using index`
380
+ ==== `Using index`
381
381
382
382
说明查询是覆盖了索引的,不需要读取数据文件,从索引树(索引文件)中即可获得信息。如果同时出现 `using where`,表明索引被用来执行索引键值的查找,没有 `using where`,表明索引用来读取数据而非执行查找动作。这是MySQL 服务层完成的,但无需再回表查询记录。
383
383
384
- ===== `Using index condition`
384
+ ==== `Using index condition`
385
385
386
386
这是 MySQL 5.6 出来的新特性,叫做“索引条件推送”。简单说一点就是 MySQL 原来在索引上是不能执行如 `like` 这样的操作的,但是现在可以了,这样减少了不必要的 I/O 操作,但是只能用在二级索引上。
387
387
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)`
391
391
392
392
使用了连接缓存:Block Nested Loop,连接算法是块嵌套循环连接;Batched Key Access,连接算法是批量索引连接。
393
393
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`
397
397
398
398
用临时表保存中间结果,常用于 `GROUP BY` 和 `ORDER BY` 操作中,一般看到它说明查询需要优化了,就算避免不了临时表的使用也要尽量避免硬盘临时表的使用。
399
399
400
- ===== `Using where`
400
+ ==== `Using where`
401
401
402
402
使用了 `WHERE` 从句来限制哪些行将与下一张表匹配或者是返回给用户。注意:`Extra` 列出现 `Using where` 表示MySQL 服务器将存储引擎返回服务层以后再应用 `WHERE` 条件过滤。
403
403
@@ -409,8 +409,8 @@ FROM actor
409
409
WHERE actor_id > 100;
410
410
----
411
411
412
- ===== `Using where with pushed condition`
413
- ===== `Zero limit`
412
+ ==== `Using where with pushed condition`
413
+ ==== `Zero limit`
414
414
415
415
查询有 `LIMIT 0` 子句,所以导致不能选出任何一行。
416
416
0 commit comments