前文说了EXPLAIN的输出的含义,本文实战一下。

Database Schema

DROP DATABASE dbTest;
CREATE DATABASE dbTest;
USE dbTest;
CREATE TABLE t1
(
c_primary_key INT,
c_unique_key CHAR(64),
c_unique_not_null_key CHAR(64) NOT NULL,
c_key CHAR(64),
c_multi_key_part1 CHAR(64),
c_multi_key_part2 CHAR(64),
c_int_value INT,
c_str_value CHAR(64),
PRIMARY KEY(c_primary_key),
UNIQUE KEY(c_unique_key),
UNIQUE KEY(c_unique_not_null_key),
KEY(c_multi_key_part1, c_multi_key_part2),
KEY(c_key)
)ENGINE=InnoDB;
CREATE TABLE t2
(
c_primary_key INT,
c_unique_key CHAR(64),
c_unique_not_null_key CHAR(64) NOT NULL,
c_key CHAR(64),
c_multi_key_part1 CHAR(64),
c_multi_key_part2 CHAR(64),
c_int_value INT,
c_str_value CHAR(64),
c_t1_primary_key INT,
PRIMARY KEY(c_primary_key),
UNIQUE KEY(c_unique_key),
UNIQUE KEY(c_unique_not_null_key),
KEY(c_multi_key_part1, c_multi_key_part2),
KEY(c_key),
UNIQUE KEY(c_t1_primary_key)
)ENGINE=InnoDB;

Join类型

const
  1. 使用primary key查找一条记录,满足const条件。

    mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_primary_key=1;
    +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
    | 1 | SIMPLE | t1 | const | PRIMARY | PRIMARY | 4 | const | 1 | NULL |
    +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
  2. 使用unique key查找一条非NULL记录,满足const条件。

    mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_unique_key='4cb15758c8e311e5b46f06af68695f49';
    +----+-------------+-------+-------+---------------+--------------+---------+-------+------+-------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+---------------+--------------+---------+-------+------+-------+
    | 1 | SIMPLE | t1 | const | c_unique_key | c_unique_key | 65 | const | 1 | NULL |
    +----+-------------+-------+-------+---------------+--------------+---------+-------+------+-------+
  3. 使用unique key查找NULL记录,这种情况下NULL记录可能有多条,所以不满足const条件。而是ref条件,ref意味着可能得到匹配的结果不唯一,即可能存在多条。那么对于unique key,可以为NULL,那么我们再来看:

    mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_unique_key is NULL;
    +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
    | 1 | SIMPLE | t1 | ref | c_unique_key | c_unique_key | 65 | const | 1 | Using index condition |
    +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
  4. 使用非NULL的unique key来查询,跟primary key类似,都是唯一的,所以满足const条件。

    mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_unique_not_null_key='00047412c96511e5844906af68695f49' limit 1;
    +----+-------------+-------+-------+-----------------------+-----------------------+---------+-------+------+-------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+-----------------------+-----------------------+---------+-------+------+-------+
    | 1 | SIMPLE | t1 | const | c_unique_not_null_key | c_unique_not_null_key | 64 | const | 1 | NULL |
    +----+-------------+-------+-------+-----------------------+-----------------------+---------+-------+------+-------+
  5. 使用非唯一的index列查询,可能存在多条记录,所以是ref而不是const。

    mysql> EXPLAIN SELECT * FROM dbTest.t1 WHERE c_key='4cb15758c8e311e5b46f06af68695f49';
    +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
    | 1 | SIMPLE | t1 | ref | c_key | c_key | 65 | const | 1 | Using index condition |
    +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+

    const至多有一条记录满足条件!

eq_ref

前文说,eq_ref类型对于之前表的每一个行组合,只从该表中读取一条记录。只有一条记录匹配要求,索引必须是primary key或者unique key(非NULL)。

  1. 使用primary key进行表关联

    mysql> EXPLAIN SELECT * FROM dbTest.t1,dbTest.t2 WHERE t1.c_primary_key=t2.c_key;
    +----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------------+
    | 1 | SIMPLE | t2 | ALL | c_key | NULL | NULL | NULL | 8042 | Using where |
    | 1 | SIMPLE | t1 | eq_ref | PRIMARY | PRIMARY | 4 | dbTest.t2.c_key | 1 | Using where |
    +----+-------------+-------+--------+---------------+---------+---------+-----------------+------+-------------+

    可以看到,对于t2表中的每一行,t1中都有唯一的一行(至多一行)进行匹配,所以最终匹配为eq_ref。

  2. 使用NOT NULL的unique key(唯一的一行)进行关联

    mysql> EXPLAIN SELECT * FROM dbTest.t1,dbTest.t2 WHERE t1.c_unique_not_null_key=t2.c_unique_not_null_key;
    +----+-------------+-------+--------+-----------------------+-----------------------+---------+---------------------------------+------+-------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+--------+-----------------------+-----------------------+---------+---------------------------------+------+-------+
    | 1 | SIMPLE | t1 | ALL | c_unique_not_null_key | NULL | NULL | NULL | 9307 | NULL |
    | 1 | SIMPLE | t2 | eq_ref | c_unique_not_null_key | c_unique_not_null_key | 64 | dbTest.t1.c_unique_not_null_key | 1 | NULL |
    +----+-------------+-------+--------+-----------------------+-----------------------+---------+---------------------------------+------+-------+

    eq_ref至多有一条记录满足条件!

ref

对于之前表的每一个组合,匹配到索引值的所有记录将被读取。例如匹配那些左侧前缀的key(multi-part key),或者非primary key,或者非unique index(匹配值不是NULL),或者unique index(但是匹配值是NULL)。

  1. 使用index列匹配多行记录

    mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_key='58a95fbac96511e5844906';
    +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
    | 1 | SIMPLE | t1 | ref | c_key | c_key | 65 | const | 1 | Using index condition |
    +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------+
  2. 多表关联时使用index列匹配多行记录

    mysql> EXPLAIN SELECT * FROM t1,t2 WHERE t1.c_unique_key=t2.c_key;
    +----+-------------+-------+------+---------------+-------+---------+------------------------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+------+---------------+-------+---------+------------------------+------+-------------+
    | 1 | SIMPLE | t1 | ALL | c_unique_key | NULL | NULL | NULL | 9307 | Using where |
    | 1 | SIMPLE | t2 | ref | c_key | c_key | 65 | dbTest.t1.c_unique_key | 1 | NULL |
    +----+-------------+-------+------+---------------+-------+---------+------------------------+------+-------------+
  3. 匹配左侧前缀(t1.c_multi_key_part1)的多行记录

    mysql> EXPLAIN SELECT * FROM t1,t2 WHERE t1.c_multi_key_part1=t2.c_key;
    +----+-------------+-------+------+-------------------+-------------------+---------+-----------------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+------+-------------------+-------------------+---------+-----------------+------+-------------+
    | 1 | SIMPLE | t2 | ALL | c_key | NULL | NULL | NULL | 4904 | Using where |
    | 1 | SIMPLE | t1 | ref | c_multi_key_part1 | c_multi_key_part1 | 65 | dbTest.t2.c_key | 1 | NULL |
    +----+-------------+-------+------+-------------------+-------------------+---------+-----------------+------+-------------+
  4. 使用unique index查找NULL的记录

    mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_unique_key is NULL;
    +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+
    | 1 | SIMPLE | t1 | ref | c_unique_key | c_unique_key | 65 | const | 1 | Using index condition |
    +----+-------------+-------+------+---------------+--------------+---------+-------+------+-----------------------+

    ref可以匹配多行记录!

ref_or_null

从这个关键词可以看出,ref或者NULL,既在ref的基础上加上NULL的搜索。以下例子对应于ref的记录。

  1. 使用index列匹配多行记录,或者NULL记录

    mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_key='58a95fbac96511e5844906' or t1.c_key is NULL;
    +----+-------------+-------+-------------+---------------+-------+---------+-------+------+-----------------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------------+---------------+-------+---------+-------+------+-----------------------+
    | 1 | SIMPLE | t1 | ref_or_null | c_key | c_key | 65 | const | 2 | Using index condition |
    +----+-------------+-------+-------------+---------------+-------+---------+-------+------+-----------------------+
  2. 其余不再给输出,参照ref,SQL如下。

    mysql> EXPLAIN SELECT * FROM t1,t2 WHERE t1.c_unique_key=t2.c_key or t1.c_unique_key is NULL;
    mysql> EXPLAIN SELECT * FROM t1,t2 WHERE t1.c_multi_key_part1=t2.c_key or t1.c_multi_key_part1 is NULL;

    ref_or_null = ref + NULL记录,所以是多行

range

对于一个给定的range,使用index来获取记录。range可以使用=,<>,>,>=,BETWEEN,IN()等操作符。

  1. BETWEEN操作符

    mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_primary_key BETWEEN 10 AND 20;
    +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
    | 1 | SIMPLE | t1 | range | PRIMARY | PRIMARY | 4 | NULL | 10 | Using where |
    +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
  2. IN操作符

    mysql> EXPLAIN SELECT * FROM t1 WHERE t1.c_primary_key IN (10, 20);
    +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+
    | 1 | SIMPLE | t1 | range | PRIMARY | PRIMARY | 4 | NULL | 2 | Using where |
    +----+-------------+-------+-------+---------------+---------+---------+------+------+-------------+

等等,不再赘述。

range使用index,多条记录!

index

该类型跟ALL类型,但是不同之处在于搜索的是index数据,因为index数据比较小,所以效率肯定比ALL要高。分为两种情况:

  1. 索引数据足够满足要求,即索引数据包括了查询的所有数据(在InnoDB下,索引数据数据存储的内容,包括 索引列+主键列,如下,因为c_key索引数据包括了c_key列值+c_primary_key列值,所以只需遍历索引即可)

    mysql> EXPLAIN SELECT c_primary_key,c_key FROM t1;
    +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
    | 1 | SIMPLE | t1 | index | NULL | c_key | 65 | NULL | 4915 | Using index |
    +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
  2. 需要根据某个索引列的顺序进行查询,如下。第一条EXPLAIN从t1中select出所有的c_primary_key,这是不需要用到c_key(当然用c_key是也可以的)。但是第二条EXPLAIN由于需要按照c_key的进行排序(而c_key index就是有序的),所以只需要c_key index存储的顺序读取出来即可达到排序的功能,所以使用c_key就起到了排序的作用。

    mysql> EXPLAIN SELECT c_primary_key FROM t1;
    +----+-------------+-------+-------+---------------+-----------------------+---------+------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+---------------+-----------------------+---------+------+------+-------------+
    | 1 | SIMPLE | t1 | index | NULL | c_unique_not_null_key | 64 | NULL | 4915 | Using index |
    +----+-------------+-------+-------+---------------+-----------------------+---------+------+------+-------------+
    mysql> EXPLAIN SELECT c_primary_key FROM t1 order by c_key;
    +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+
    | 1 | SIMPLE | t1 | index | NULL | c_key | 65 | NULL | 4915 | Using index |
    +----+-------------+-------+-------+---------------+-------+---------+------+------+-------------+

    index这种情况也就比ALL要好一点,这种SQL需要重点review,以防带来灾难!

ALL

终于到了最差的情况,全表扫描。即没有合适的索引数据,所以只能一行一行的扫描数据了,沦落至此,可想而知效果极差。例如:

  1. 查询符合条件的记录,如下,由于c_str_value列没有索引,导致只能进行全表扫描。优化方法:可以在c_str_value列上加上索引。

    mysql> EXPLAIN SELECT * FROM t1 where t1.c_str_value='1111';
    +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
    | 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 4915 | Using where |
    +----+-------------+-------+------+---------------+------+---------+------+------+-------------+
  2. 查询一个表的所有记录,如下。优化方法:在满足业务需求的情况下,把查询的所有列改成某几列,这样若是某个索引数据满足条件的话,可以不用遍历全表,而仅仅遍历索引数据即可。

    mysql> EXPLAIN SELECT * FROM t1;
    +----+-------------+-------+------+---------------+------+---------+------+------+-------+
    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    +----+-------------+-------+------+---------------+------+---------+------+------+-------+
    | 1 | SIMPLE | t1 | ALL | NULL | NULL | NULL | NULL | 4915 | NULL |
    +----+-------------+-------+------+---------------+------+---------+------+------+-------+

    最慢的查询,必须得review这些SQL,数据量大的情况下,必然带来灾难!

总结

通过以上,我们可以看到效率排序为: const < eq_ref < ref < ref_or_null(range) < index < ALL,通常index和ALL是需要重点注意的。让我们的嗅觉灵敏起来吧。:)

本着理论指导实践的原则,以上用实例对理论做了实践,难免出错,敬请指正。

遗留问题:

  1. unique index如何保存NULL的索引?这个key允许多条NULL记录存在么?

  2. primary index可以为NULL么?
  3. index列中如何存储NULL数据?

SQL优化:使用explain的更多相关文章

  1. 浅谈SQL优化入门:2、等值连接和EXPLAIN(MySQL)

    1.等值连接:显性连接和隐性连接 在<MySQL必知必会>中对于等值连接有提到两种方式,第一种是直接在WHERE子句中规定如何关联即可,那么第二种则是使用INNER JOIN关键字.如下例 ...

  2. Spring+SpringMVC+MyBatis+easyUI整合优化篇(十二)数据层优化-explain关键字及慢sql优化

    本文提要 从编码角度来优化数据层的话,我首先会去查一下项目中运行的sql语句,定位到瓶颈是否出现在这里,首先去优化sql语句,而慢sql就是其中的主要优化对象,对于慢sql,顾名思义就是花费较多执行时 ...

  3. SQL优化 MySQL版 -分析explain SQL执行计划与笛卡尔积

    SQL优化 MySQL版 -分析explain SQL执行计划 作者 Stanley 罗昊 [转载请注明出处和署名,谢谢!] 首先我们先创建一个数据库,数据库中分别写三张表来存储数据; course: ...

  4. mysql 开发进阶篇系列 2 SQL优化(explain分析)

    接着上一篇sql优化来说 1. 定位执行效率较低的sql 语句 通过两种方式可以定位出效率较低的sql 语句. (1) 通过上篇讲的慢日志定位,在mysqld里写一个包含所有执行时间超过 long_q ...

  5. SQL优化笔记一:索引和explain

    目录 为什么需要优化SQL SQL优化的重点 索引 索引的结构 索引的优缺点总结: 索引的分类 索引操作 B树 实战 问题 数据库方面,我会使用MySQL来讲解 为什么需要优化SQL 性能低,执行时间 ...

  6. Explain 执行计划 和 SQL优化

    Explain 介绍 在分析查询性能时,考虑EXPLAIN关键字同样很管用.EXPLAIN关键字一般放在SELECT查询语句的前面,用于描述MySQL如何执行查询操作.以及MySQL成功返回结果集需要 ...

  7. EXPLAIN sql优化方法(2) Using temporary ; Using filesort

    优化GROUP BY语句   默认情况下,MySQL对所有GROUP BY col1,col2...的字段进行排序.这与在查询中指定ORDER BY col1,col2...类似.因此,如果显式包括一 ...

  8. SQL优化(三)—— 索引、explain分析

    SQL优化(三)—— 索引.explain分析   一.什么是索引 索引是一种排好序的快速查找的数据结构,它帮助数据库高效的查询数据 在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据 ...

  9. SQL优化之慢查询和explain以及性能分析

    性能优化的思路 首先需要使用慢查询功能,去获取所有查询时间比较长的SQL语句 使用explain去查看该sql的执行计划 使用show profile去查看该sql执行时的性能问题 MySQL性能优化 ...

  10. 不会看 Explain执行计划,劝你简历别写熟悉 SQL优化

    昨天中午在食堂,和部门的技术大牛们坐在一桌吃饭,作为一个卑微技术渣仔默默的吃着饭,听大佬们高谈阔论,研究各种高端技术,我TM也想说话可实在插不上嘴. 聊着聊着突然说到他上午面试了一个工作6年的程序员, ...

随机推荐

  1. Python---1. 基础数据类型

    转载:  Py西游攻关之基础数据类型

  2. bat笔记

    背景介绍 现入职的公司包含发送EDM的项目,每天都有各种题型邮件需要发送,但是由于各种原因,发送EDM程序的服务器老是被网管各种重启 :) 作为负责人,对这事很恼火,隔几天就被投诉,怎么又没收到考勤邮 ...

  3. Android中解析XML格式数据的方法

    XML介绍:Extensible Markup Language,即可扩展标记语言 一.概述 Android中解析XML格式数据大致有三种方法: SAX DOM PULL 二.详解 2.1 SAX S ...

  4. Dom 重绘重排

    https://juejin.im/entry/590801780ce46300617c89b8   DOM 重绘重排

  5. JavaScript高级内容笔记:原型链、继承、执行上下文、作用域链、闭包

    最近在系统的学习JS深层次内容,并稍微整理了一下,作为备忘和后期复习,这里分享给大家,希望对大家有所帮助.如有错误请留言指正,tks. 了解这些问题,我先一步步来看,先从稍微浅显内容说起,然后引出这些 ...

  6. 20145211《网络对抗》逆向及BOF基础实践

    逆向及BOF基础实践——又是一年梅落时 一.实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串. ...

  7. 20145311 《Java程序设计》第十周学习总结

    20145311 <Java程序设计>第十周学习总结 教材学习内容总结 网络编程 ·网络编程就是在两个或两个以上的设备(例如计算机)之间传输数据·程序员所作的事情就是把数据发送到指定的位置 ...

  8. 2017-2018-1 JaWorld 团队作业--冲刺3

    2017-2018-1 JaWorld 团队作业--冲刺3 (20162306) 总体架构 我们本次团队项目设定为基于Android系统Java架构下的打飞机小游戏 游戏中所有模型的原型设定是精灵,因 ...

  9. springboot属性类自动加载配置文件中的值

    springboot属性类自动加载配置文件中的值,如Person类加载在yml中配置的name,age等属性值,可以通过如下步骤获取: 类上添加@ConfigurationProperties注解,p ...

  10. [QA翻译]如何在Storm里拆分stream流?

    原文:http://stackoverflow.com/questions/19807395/how-would-i-split-a-stream-in-apache-storm 问题:我现在不清楚如 ...