MySQL 执行计划 explain plan

MySQL :: MySQL 5.6 Reference Manual :: 8.8 Understanding the Query Execution Plan

取决于tables、columns、indexes的详细情况和where条件,MySQL优化器考虑了很多技术来有效的进行SQL Query中的lookup。
大表的查询可以不需要读取所有的行;多个表的连接可以不用对比每一个行组合。
优化器选择了一系列操作实现最高效的查询,这些操作的集合称为“query execution plan”,被大家熟知为EXPLAIN plan
你需要接受EXPLAIN plan显示查询优化良好的一面,但同时需要掌握SQL语法和索引技术以便在查询效率不好的时候进行优化。

一、Optimizing Queries with EXPLAIN

EXPLAIN 语句提供了MySQL是怎么执行语句的相关信息:

通过EXPLAIN,你可以看到哪里需要添加索引,通过索引查找行将使得语句执行更快。还可以使用EXPLAIN来检查optimizer是否以最优顺序join tables。要指定optimizer使用与SELECT语句中表的命名顺序相对应的顺序,请在语句开头使用SELECT STRIGHT_join,而不是SELECT
参考Section 13.2.9, “SELECT Statement”
但是,STRAIGHT_JOIN可能会阻止使用索引,因为它禁用半连接转换。
参见Section 8.2.2.1, “Optimizing Subqueries with Semijoin Transformations”.

如果遇到索引未被使用的问题,可以运行ANALYZE TABLE来更新表统计信息,例如键的基数,这可能会影响optimizer所做的选择。
参见 Section 13.7.2.1, “ANALYZE TABLE Statement”

EXPLAIN还可以用于获取表中列的信息。EXPLAIN *tbl_name* 等同于DESCRIBE *tbl_name*SHOW COLUMNS FROM *tbl_name*
参见 Section 13.8.1, “DESCRIBE Statement”Section 13.7.5.6, “SHOW COLUMNS Statement”.

二、EXPLAIN Output Format

1. EXPLAIN Output Columns
# EXPLAIN 
+----+--------------------+----------------------+--------+---------------------------------------------------------+------------------------+---------+-----------------------------------+--------+------------------------------------+
| id | select_type        | table                | type   | possible_keys                                           | key                    | key_len | ref                               | rows   | Extra                              |
+----+--------------------+----------------------+--------+---------------------------------------------------------+------------------------+---------+-----------------------------------+--------+------------------------------------+
|  1 | PRIMARY            | b                    | index  | PRIMARY,IDX_lcgrpper_insuredno                          | IDX_lcgrpper_insuredno | 63      | NULL                              | 396391 | Using where; Using index           |
|  1 | PRIMARY            | a                    | ref    | IDX_INSUREDNO_NO,IDX_LCINSURED_GPCONT,IDX_LCINSURED_GCI | IDX_INSUREDNO_NO       | 74      | lis.b.InsuredNo                   |      1 | Using index condition; Using where |
|  5 | SUBQUERY           | lcgrppersonalvoucher | index  | NULL                                                    | IDX_lcgrpper_insuredno | 63      | NULL                              | 396391 | Using where; Using index           |
|  4 | DEPENDENT SUBQUERY | ldcode               | eq_ref | PRIMARY                                                 | PRIMARY                | 184     | const,lis.a.relationtomaininsured |      1 | Using where                        |
|  3 | DEPENDENT SUBQUERY | ldcode               | eq_ref | PRIMARY                                                 | PRIMARY                | 184     | const,lis.a.idtype                |      1 | Using where                        |
|  2 | DEPENDENT SUBQUERY | ldcode               | eq_ref | PRIMARY                                                 | PRIMARY                | 184     | const,lis.a.sex                   |      1 | Using where                        |
+----+--------------------+----------------------+--------+---------------------------------------------------------+------------------------+---------+-----------------------------------+--------+------------------------------------+


# EXPLAIN EXTENDED
+----+--------------------+----------------------+--------+---------------------------------------------------------+------------------------+---------+-----------------------------------+--------+----------+------------------------------------+
| id | select_type        | table                | type   | possible_keys                                           | key                    | key_len | ref                               | rows   | filtered | Extra                              |
+----+--------------------+----------------------+--------+---------------------------------------------------------+------------------------+---------+-----------------------------------+--------+----------+------------------------------------+
|  1 | PRIMARY            | b                    | index  | PRIMARY,IDX_lcgrpper_insuredno                          | IDX_lcgrpper_insuredno | 63      | NULL                              | 396391 |   100.00 | Using where; Using index           |
|  1 | PRIMARY            | a                    | ref    | IDX_INSUREDNO_NO,IDX_LCINSURED_GPCONT,IDX_LCINSURED_GCI | IDX_INSUREDNO_NO       | 74      | lis.b.InsuredNo                   |      1 |   100.00 | Using index condition; Using where |
|  5 | SUBQUERY           | lcgrppersonalvoucher | index  | NULL                                                    | IDX_lcgrpper_insuredno | 63      | NULL                              | 396391 |   100.00 | Using where; Using index           |
|  4 | DEPENDENT SUBQUERY | ldcode               | eq_ref | PRIMARY                                                 | PRIMARY                | 184     | const,lis.a.relationtomaininsured |      1 |   100.00 | Using where                        |
|  3 | DEPENDENT SUBQUERY | ldcode               | eq_ref | PRIMARY                                                 | PRIMARY                | 184     | const,lis.a.idtype                |      1 |   100.00 | Using where                        |
|  2 | DEPENDENT SUBQUERY | ldcode               | eq_ref | PRIMARY                                                 | PRIMARY                | 184     | const,lis.a.sex                   |      1 |   100.00 | Using where                        |
+----+--------------------+----------------------+--------+---------------------------------------------------------+------------------------+---------+-----------------------------------+--------+----------+------------------------------------+

Column JSON Name Meaning
id select_id The SELECT identifier
select_type None The SELECT type
table table_name The table for the output row
partitions partitions The matching partitions. Only if the PARTITIONS keyword is used
type access_type The join type
possible_keys possible_keys MySQL可能使用的索引。如果是NULL,则无索引,需要添加。此列完全独立于EXPLAIN输出中显示的表格顺序。这意味着一些key在实际生成的表顺序中可能不可用。
key key The index actually chosen
key_len key_length The length of the chosen key
ref ref The columns compared to the index
rows rows Estimate of rows to be examined
filtered filtered Percentage of rows filtered by table condition
Extra None Additional information

select_type

select_type JSON Name Meaning
SIMPLE None Simple SELECT (not using UNION or subqueries)
PRIMARY None Outermost SELECT
UNION None Second or later SELECT statement in a UNION
DEPENDENT UNION dependent (true) Second or later SELECT statement in a UNION, dependent on outer query
UNION RESULT union_result Result of a UNION
SUBQUERY None First SELECT in subquery
DEPENDENT SUBQUERY dependent (true) First SELECT in subquery, dependent on outer query
DERIVED None Derived table
MATERIALIZED materialized_from_subquery Materialized subquery
UNCACHEABLE SUBQUERY cacheable (false) A subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query
UNCACHEABLE UNION cacheable (false) The second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY)
2. EXPLAIN Join Types

从最好到最差:

  • system
    表仅有一行 (= system table)。const的一种特殊存在。

  • const
    表最多只有一个匹配行,在开始查询的时候就会读取到。因为仅有一行,该行中列的值会被optimizer的其余部分视为常量constants。 const tables 非常快,仅为它们仅读取一次。

    const 用在对PRIMARY KEY or UNIQUE index的所有部分跟常量值constant values比对的时候。 在如下的查询中,tbl_name 可以作为一个const table:

SELECT * FROM tbl_name WHERE primary_key=1;

SELECT * FROM tbl_name
  WHERE primary_key_part1=1 AND primary_key_part2=2;
  • eq_ref
    对于每个联合,仅从该表读取一行。除system and const类型外,这可能是最好的 join type。在使用了索引的全部并且该索引是PRIMARY KEY or UNIQUE NOT NULL index的情况下,使用该类型。
    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;
  • ref
    对于每个联合,所有匹配索引的行都读取自该表。如果联接仅使用Key的最左侧前缀,或者Key不是 PRIMARY KEY or UNIQUE index(换句话说,联接无法基于key选择唯一row),则使用ref。如果使用的Key只匹配数行,则这是一种良好的join type。
    ref 可以用于通过 = or <=> operator进行对比的,拥有索引的列。如下范例,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
    联接使用 FULLTEXT 索引。

  • ref_or_null
    类似于ref,但是有一个查询是否包含NULL值的额外检索。这种join type优化常用于子查询。如下范例, MySQL 可以使用ref_or_null 联接到进程 ref_table:

SELECT * FROM ref_table
  WHERE key_column=expr OR key_column IS NULL;

参见 Section 8.2.1.12, “IS NULL Optimization”.

  • index_merge
    索引合并优化。该情况下,key列包含所用索引的列表,key_len包含所用索引的最长key部分的列表。
    参见Section 8.2.1.3, “Index Merge Optimization”

  • unique_subquery
    IN子查询中替换eq_ref类型,如下格式:

value IN (SELECT primary_key FROM single_table WHERE some_expr)

unique_subquery 是彻底替换subquery的更高效的索引查询功能。

  • index_subquery
    类似于unique_subquery。它替代 IN 子查询,但是它工作于非唯一索引,如下格式:
value IN (SELECT key_column FROM single_table WHERE some_expr)
  • range
    在给定范围内,使用索引检索。key列显示了使用哪些索引,key_len列包含了使用的最长的key。该类型下, ref 列为 NULL
    range 适用于 key 列对比常量,通过 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, or IN() 操作符:
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
    index join type 与ALL同,只是扫描了索引树。发生于如下两种方式:

    • 如果索引是查询的covering index,并且可以用于满足表中所需的所有数据,则只需扫描索引树。该情况下,Extra显示Using index。仅扫描索引通常比ALL快,因为索引的大小通常小于表数据。

    • 使用从索引中读取的内容执行全表扫描,按索引顺序查找数据。Uses index 不会出现在 Extra 列中。

    当查询使用列是唯一索引一部分,使用该联接类型。

  • ALL
    对每个联合都进行全表扫描。如果该表是没有标记为const的第一个表,这通常是不好的;其他情况下则非常糟糕。通常,可以通过添加索引来避免ALL,这些索引允许基于常量或更早表的列值从表中检索行。

3. EXPLAIN Extra Information
4. EXPLAIN Output Interpretation

三、Extended EXPLAIN Output Format

四、Estimating Query Performance

大多数情况下,你可以通过磁盘查找来估算查询性能。对于小表,通常仅需要一次磁盘查找就可以找到该行(因为索引缓存了)。对于大表,可以估算,使用B-tree索引,需要如下很多次查询才能获取到该行:

log(row_count) / log(index_block_length / 3 * 2 / (index_length + data_pointer_length)) + 1

在MySQL,一个index block通常是1,024 bytes, data pointer通常是4 bytes。对于一个键值长度3字节(the size of MEDIUMINT)、50万行的表,公式显示 log(500,000)/log(1024/3*2/(3+4)) + 1 = 4 次seek请求。

这个索引需要大约50000073/2=5.2MB的存储空间(假设典型的索引缓冲区填充率为2/3),因此内存中可能有很多索引,因此只需要一个或两个调用来读取数据以查找行。

索引需要大概500,000 * 7 * 3/2 = 5.2MB的存储空间,这里假设index buffer填充率通常为2/3,所以内存中可能有很多索引,只需要一个或两个调用来读取数据以查找行。

对于写操作,需要四个seek请求来确认放置新索引值的位置,通常需要两个seek请求来更新索引并写入行。

前面的讨论并不意味着您的应用程序会慢慢下降 log N的性能。只要所有内容被OS或MySQL缓存,表变大只会稍微变慢。在数据变得太大而无法缓存之后,才会变得慢得多,直到应用程序受限于磁盘请求(增长 log N)。
要避免这种情况,key cache需要随着数据的增长而增加。
对于MyISAM表,key cache size由key_buffer_size 系统变量控制。参考 Section 5.1.1, “Configuring the Server”

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容