前文说了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. thinkphp5使用PHPMailler发送邮件

    http://www.dawnfly.cn/article-1-350.html 想要了解thinkphp3.2版本发送邮件的,请点击此链接:http://www.dawnfly.cn/article ...

  2. Windows Server 2008 R2(X64) MSDN镜像简体中文版与英文版ISO下载及Key激活码

    Windows Server 2008 R2 MSDN ISO镜像简体中文版 文件名:cn_windows_server_2008_r2_standard_enterprise_datacenter_ ...

  3. windows下和linux下libcurl安装

    下载源代码,在winbuild里面有个说明文件: Building with Visual C++, prerequisites==================================== ...

  4. 前端基础(jQuery)

    jquery: JS Bootstrap jquery: write less do more jquery对象: Jquery.方法 ======= $.方法 jquery的基础语法:$(selec ...

  5. FILE 文件的使用 (VC、BCB、Qt)

    FILE * fp ;AnsiString filePath="";fp= fopen(filePath.c_str(),"wb");//第二个参数是文件打开方 ...

  6. 53. Maximum Subarray(动态规划 求最大子数组)

      Find the contiguous subarray within an array (containing at least one number) which has the larges ...

  7. Good Bye 2018 Solution

    A. New Year and the Christmas Ornament 签到. #include <bits/stdc++.h> using namespace std; int a ...

  8. 【运维技术】CentOS7上从零开始安装阿里RocketMQ版本:release-4.0.1【亲测哈哈】

    CentOS7上从零开始安装阿里RocketMQ版本:release-4.0.1[亲测哈哈] 安装git # 更新包 $ yum update # 安装git $ yum install git # ...

  9. Spring JPA中OneToOne和OneToMany用法

    Spring工程中,创建实体对象时,可以通过JPA的@Entity标识实体与数据库表的对应关系,@Column标识数据库字段.其中还有标识两个实体间关系的注解:@OneToOne.@OneToMany ...

  10. HDU1510 White rectangles( 乱搞 O(n^3) )题解

    思路: 友谊赛的时候一直想到了,但是没想出来怎么遍历才能找到所有矩阵,卡住了. 这里讲一下完整思路:我们用一个num[i][j]表示第i行第j列每一列连续的白色格子数量,然后我们定义一个MIN,并且每 ...