前文说了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. PAT 1110 Complete Binary Tree[判断完全二叉树]

    1110 Complete Binary Tree(25 分) Given a tree, you are supposed to tell if it is a complete binary tr ...

  2. 排序问题Java

    package zhuzhuangtu; import java.util.*; import java.io.*; public class Main{ public static void mai ...

  3. cocos代码研究(19)Widget子类ImageView学习笔记

    理论基础 显示图片的小控件,继承自 Widget . 代码实践 static ImageView * create()创建一个空的ImageView static ImageView * create ...

  4. 11月26号host

    127.0.0.1 localhost255.255.255.255 broadcasthost::1 localhostfe80::1%lo0 localhost # Google start216 ...

  5. hdu1199 线段树

    这题说的是给了 n 个操作. 每个操作会把 [a,b] 之间的球 涂为黑色或者 白色, 然后最后问 最长的连续的白色的 球有多少个,初始的时候全是黑的. 我们将所有的点离散化, 记得离散 a-1, b ...

  6. Android ActionBar自定义

    关于自定义的ActionBar的实现过程,这里做下笔记以供之后查看. 1.默认状态 使用Android Studio新建一个名为“ActionBar”的应用,默认actionbar如图(1)所示. 图 ...

  7. LoRa无线通信设计(一)原理

    LoRa无线通信设计(一)原理 引言 1901年,古列尔默.马可尼把长波无线电信号从Cornwall(康沃尔,位于英国的西南部)跨过大西洋传送到3200公里之外的Newfoundland(加拿大的纽芬 ...

  8. 【转载】package-info

    本文是转载,原文地址:http://strong-life-126-com.iteye.com/blog/806246 package-info.java对于经常使用外部包的程序员来说应该是熟悉陌生人 ...

  9. 在apache中使用.htaccess文件的注意事项

    在apache的配置文件中: <VirtualHost *:80> ServerName tp5.com DocumentRoot d:/wamp/www/tp5.com/public & ...

  10. nw.js node-webkit系列(18)怎么对.js进行编译以防你的代码暴露出来

    原文链接:http://blog.csdn.net/zeping891103/article/details/50819102 参考:https://segmentfault.com/a/119000 ...