SQL优化:使用explain

前文说了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数据?
时间: 2024-08-05 23:41:34

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

【MySQL笔记】SQL优化利器 - explain命令的输出格式详解

有MySQL使用经验的同学在实际项目中可能会遇到SQL慢查询的场景,有些场景很容易定位问题所在(如单表操作有慢查询SQL时,仔细check SQL语句通常很容易定位索引问题),而有些复杂业务场景下(如多表联合查询几十个字段并做group或sort等操作),人工check SQL语句通常很难发现SQL瓶颈根源.这个时候,MySQL提供的explain命令就派上用场了. 本笔记主要对explain的输出结果做说明,并给出根据explain输出对SQL做优化的思路. 1. EXPLAIN语法及用途 e

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

1.等值连接:显性连接和隐性连接 在<MySQL必知必会>中对于等值连接有提到两种方式,第一种是直接在WHERE子句中规定如何关联即可,那么第二种则是使用INNER JOIN关键字.如下例两种方式是"等同"的. //WHERE方式 SELECT vend_name, prod_name, prod_price, quantity FROM vendors, products, orderitems WHERE vendors.vend_id = products.vend_

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

优化GROUP BY语句   默认情况下,MySQL对所有GROUP BY col1,col2...的字段进行排序.这与在查询中指定ORDER BY col1,col2...类似.因此,如果显式包括一个包含相同的列的ORDER BY子句,则对MySQL的实际执行性能没有什么影响. 如果查询包括GROUP BY 但用户想要避免排序结果的消耗,则可以指定ORDER By NULL禁止排序,例如:explain select id, sum(moneys) from sales2 group by i

逆水行舟 —— SQL优化之慢查询和explain以及性能分析

性能优化的思路 首先需要使用慢查询功能,去获取所有查询时间比较长的SQL语句 使用explain去查看该sql的执行计划 使用show profile去查看该sql执行时的性能问题 MySQL性能优化之慢查询 数据库的查询速度是影响项目性能的重要因素,除了添加缓存中间件外,对于查询本身的优化带来的性能也是不容小觑 要想优化查询SQL,就应该先找打需要被优化的SQL语句,MySQL提供了这么一个功能可以帮助我们快速定位带查询慢的SQL MySQL的慢查询日志功能:默认关闭,需要手动开启 查看是否开

基于案例SQL优化第九课作业分享

默认统计信息收集: 1. 11g默认启动了统计信息收集的任务,默认运行时间是周一到周五晚上10点和周6,周天的早上6点 2. 你也可以关闭自动统计新收集任务,选择手工收集的方式,但是一般不建议这样操作. 动态统计信息: 1. 统计信息默认情况下是每天晚上10点半后收集,如果新建对象还没来得级收集统计信息,就采用动态采样的方式. 2. 具体在set autotrace 跟踪的执行计划中,可以看到类似:- dynamic sampling used for this statement (level

MySQL 数据库性能优化之SQL优化

前言 有人反馈之前几篇文章过于理论缺少实际操作细节,这篇文章就多一些可操作性的内容吧. 注:这篇文章是以 MySQL 为背景,很多内容同时适用于其他关系型数据库,需要有一些索引知识为基础. 优化目标 1.减少 IO 次数 IO永远是数据库最容易瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,减少 IO 次数是 SQL 优化中需要第一优先考虑,当然,也是收效最明显的优化手段. 2.降低 CPU 计算 除了 IO 瓶颈之外,SQL优化中需要考虑的就

mysql sql优化

前言 有人反馈之前几篇文章过于理论缺少实际操作细节.这篇文章就多一些可操作性的内容吧. 注:这篇文章是以 MySQL 为背景,非常多内容同一时候适用于其它关系型数据库,须要有一些索引知识为基础. 优化目标 1.降低 IO 次数 IO永远是数据库最easy瓶颈的地方,这是由数据库的职责所决定的,大部分数据库操作中超过90%的时间都是 IO 操作所占用的,降低 IO 次数是 SQL 优化中须要第一优先考虑.当然,也是收效最明显的优化手段. 2.减少 CPU 计算 除了 IO 瓶颈之外,SQL优化中须

mysql优化-数据库优化、SQL优化

我有一张表w1000,里面有1000万条数据,这张表结构如下:CREATE TABLE `w1000` ( `id` varchar(36) NOT NULL, `name` varchar(10) DEFAULT NULL, `age` int(3) DEFAULT NULL, `money` double(8,2) DEFAULT NULL, `address` varchar(100) DEFAULT NULL, `create_date` datetime(3) DEFAULT NULL

SQL优化的四个方面,缓存,表结构,索引,SQL语句

一,缓存 数据库属于 IO 密集型的应用程序,其主要职责就是数据的管理及存储工作.而我们知道,从内存中读取一个数据库的时间是微秒级别,而从一块普通硬盘上读取一个IO是在毫秒级别,二者相差3个数量级.所以,要优化数据库,首先第一步需要优化的就是 IO,尽可能将磁盘IO转化为内存IO. query_cache_size/query_cache_type (global) Query cache 作用于整个 MySQL Instance,主要用来缓存 MySQL 中的 ResultSet,也就是一条S

转 sql 优化

1.关于SQL查询效率,100w数据,查询只要1秒,与您分享: 机器情况p4: 2.4内存: 1 Gos: windows 2003数据库: ms sql server 2000目的: 查询性能测试,比较两种查询的性能 SQL查询效率 step by step -- setp 1.-- 建表create table t_userinfo(userid int identity(1,1) primary key nonclustered,nick varchar(50) not null defa