zoukankan      html  css  js  c++  java
  • 执行计划(EXPLAIN)

    一、about

    MySQL5.7.20
    初始数据:world.sql

    现在,让我们回想一下SQL的执行过程:

    1. 客户端将SQL发送到mysqld
    2. 经过连接层。
    3. 在SQL层,会经过、语法、语意检查、权限检查后经过解析器预处理后,产生计划(可能产生多个执行计划),优化器根据解析器得出的多种执行计划,选择一个最优执行计划,然后执行器执行SQL产生执行结果。
    4. 经过存储引擎层一顿操作后通过专用线程将结果返回给客户端。

    而本篇就来研究执行计划,通过执行计划可以了解MySQL选择了什么执行计划来执行SQL,并且SQL的执行过程到此结束,即并不会真正的往下交给执行器去执行;最终的目的还是优化MySQL的性能。

    而我们这里重点关注查询时的执行计划

    -- 基本用法
    EXPLAIN SQL语句;
    
    -- 示例
    EXPLAIN SELECT * FROM city WHERE id<3;
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | city  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    2 |   100.00 | Using where |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)

    返回中的重要参数:

    字段描述备注
    id 该SELECT标识符  
    select_type 该SELECT类型  
    table 输出结果的表  
    partitions 匹配的分区  
    type 表的连接类型  
    possible_keys 查询时可能的索引选择 只是有可能选择的索引,但是也能最后选择的索引不在该字段中
    key 实际选择的索引 需要重点了解的
    key_len 所选KEY的长度  
    ref 列与索引的比较  
    rows 表示MySQL认为执行查询必须检查的行数 innodb中是个估算值
    filtered 按表条件过滤的行百分比  
    Extra 执行情况的附加信息 需要重点了解的

    这里我们重点掌握KEYExtra字段。其他的就参考:EXPLAIN Output Format

    其次,EXPLAINSELECT语句中使用到的每个表都返回一行信息,并且按照MySQL在处理语句时读取它们的顺序列出了输出中的表:

    DESC SELECT city.name,city.population,country.code,country.name
    FROM city INNER JOIN country
    ON city.countrycode = country.code;
    +----+-------------+---------+------------+------+---------------------+-------------+---------+--------------------+------+----------+-------+
    | id | select_type | table   | partitions | type | possible_keys       | key         | key_len | ref                | rows | filtered | Extra |
    +----+-------------+---------+------------+------+---------------------+-------------+---------+--------------------+------+----------+-------+
    |  1 | SIMPLE      | country | NULL       | ALL  | PRIMARY             | NULL        | NULL    | NULL               |  239 |   100.00 | NULL  |
    |  1 | SIMPLE      | city    | NULL       | ref  | CountryCode,inx_c_p | CountryCode | 3       | world.country.Code |   18 |   100.00 | NULL  |
    +----+-------------+---------+------------+------+---------------------+-------------+---------+--------------------+------+----------+-------+
    2 rows in set, 1 warning (0.00 sec)

    MySQL使用嵌套循环连接方法解析所有连接。这意味着MySQL从第一个表中读取一行,然后在第二个表,第三个表中找到匹配的行,依此类推。处理完所有表后,MySQL通过表列表输出选定的列和回溯,直到找到一个表,其中存在更多匹配的行。从该表中读取下一行,然后继续下一个表。

    另外,DESCRIBEEXPLAIN同义词,同样可以查看语句的执行计划:

    -- 下面三条语句是等价的
    EXPLAIN SELECT * FROM city WHERE id<3;
    DESCRIBE SELECT * FROM city WHERE id<3;
    DESC SELECT * FROM city WHERE id<3;

     二、type

    type 的官方全称是join type,意思是"连接类型",这容易让人误会是联表的连接类型,其实不然,这里的join type事实上是数据库引擎查找表的一种方式,我们这里可以像在《高性能MySQL》中作者称呼join type为访问类型更贴切些。

    type列输出介绍如何联接表,接下来列举常见联接类型,性能从最佳到最差排序:

    我们挑重点的说!为了便于理解,这里从性能最差到最佳排序一一说明

     2.1 ALL

    all便是所谓的全表扫描了,如果出现了all类型,通常意味着你的SQL处于一种最原始的状态,还有很大的优化空间!

    我们来看常见出现all的情况

    1.查询条件字段是非索引字段

    EXPLAIN SELECT * FROM city WHERE district='henan';
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4188 |    10.00 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)

     2. 查询条件中,包含 !=、not in、like

    -- 注意,以下情况适用于辅助索引
    EXPLAIN SELECT * FROM city WHERE countrycode NOT IN ('CHN','USA');
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | city  | NULL       | ALL  | CountryCode   | NULL | NULL    | NULL | 4188 |    82.19 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    EXPLAIN SELECT * FROM city WHERE countrycode != 'CHN';
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | city  | NULL       | ALL  | CountryCode   | NULL | NULL    | NULL | 4188 |    88.73 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    
    -- 而聚集索引来说,还是会走索引
    EXPLAIN SELECT * FROM city WHERE id != 10;
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | city  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 2103 |   100.00 | Using where |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    
    
    -- 而针对于like情况,% 放在首位不会走索引,放在后面会走索引
    EXPLAIN SELECT * FROM city WHERE countrycode LIKE 'CH%';   -- 走索引
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
    | id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                 |
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
    |  1 | SIMPLE      | city  | NULL       | range | CountryCode   | CountryCode | 3       | NULL |  397 |   100.00 | Using index condition |
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
    1 row in set, 1 warning (0.00 sec)
    
    EXPLAIN SELECT * FROM city WHERE countrycode LIKE '%H%';    -- 不走索引
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4188 |    11.11 | Using where |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.01 sec)

    2.2 index

    index是另一种形式上的all类型,只不过index是全索引扫描(但索引也是建立在全表记录上的),index根据条件扫描索引然后回表取数据。indexall相比,它们都扫描了全表的数据,而且index要先扫索引再回表取数据,这么一说,还不如all快呢(在同样的工况下)!但是为啥官方说indexall效率高呢?原因(我经过严格的逻辑推理和分析得来的)在于索引是有序的,所以index效率高些,来看排序示例:

    EXPLAIN SELECT * FROM city ORDER BY id;        -- id 是主键
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
    | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
    |  1 | SIMPLE      | city  | NULL       | index | NULL          | PRIMARY | 4       | NULL | 4188 |   100.00 | NULL  |
    +----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)
    
    EXPLAIN SELECT * FROM city ORDER BY population;    -- 普通字段
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
    |  1 | SIMPLE      | city  | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 4188 |   100.00 | Using filesort |
    +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
    1 row in set, 1 warning (0.00 sec)

    可以看到根据population排序走的是ALL访问类型,但Extra字段说使用Using filesort;而根据主键id排序的访问类型是index,并且Extra字段是NULL,即没有额外的排序,所以这可能就是官方说indexall性能好的原因。

    还有一种情况需要我们注意,那就是Extra字段是Using index,并且typeindex

    EXPLAIN SELECT id FROM city;
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
    | id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra       |
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
    |  1 | SIMPLE      | city  | NULL       | index | NULL          | CountryCode | 3       | NULL | 4188 |   100.00 | Using index |
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)

    如上这种情况我们称为"索引覆盖",那什么是"索引覆盖"?举个例子:我们从字典(一张表)的目录(索引)中找到所有的字,而无需查每个字的实际位置,因为我们想要的数据都在索引中,这就是"索引覆盖",稍微正式点的解释:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快

    2.3 range

    range是基于索引的范围扫描,包含>,<,>=,<=,!=,like,in,not in,or,!=,not in的情况会走range

    出现range的条件是:查询条件列是非PRIMARY KEYUNIUQE KEY的索引列,也就是说条件列使用了索引,但该索引列的值并不是唯一的,这样的话,即使很快的找到了第一条数据,但仍然不能停止的在指定的范围内继续找。

    range的优点是不需要扫描全表,因为索引是有序的,即使有重复值,但也被限定在指定的范围内。

    -- 首先为 population 字段建立一个普通索引,现在 population 和 countrycode 是普通索引,而 id 是主键
    ALTER TABLE city ADD INDEX idx_p(population);
    
    -- 示例
    EXPLAIN SELECT * FROM city WHERE population < 100000;    -- 走索引的范围查找
    EXPLAIN SELECT * FROM city WHERE countrycode LIKE 'CH%';  -- 因为索引是有序的,也走索引的范围查找

    注意,上面前两个例子可以享受到B+树带来的查询优势(查询连续);而下面的例子是无法享受的(查询不连续):

    EXPLAIN SELECT * FROM city WHERE countrycode IN ('CHN','USA');
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
    | id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                 |
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
    |  1 | SIMPLE      | city  | NULL       | range | CountryCode   | CountryCode | 3       | NULL |  637 |   100.00 | Using index condition |
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
    1 row in set, 1 warning (0.01 sec)

    注意,上面前两个例子可以享受到B+树带来的查询优势(查询连续);而下面的例子是无法享受的(查询不连续):

    EXPLAIN SELECT * FROM city WHERE countrycode IN ('CHN','USA');
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
    | id | select_type | table | partitions | type  | possible_keys | key         | key_len | ref  | rows | filtered | Extra                 |
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
    |  1 | SIMPLE      | city  | NULL       | range | CountryCode   | CountryCode | 3       | NULL |  637 |   100.00 | Using index condition |
    +----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-----------------------+
    1 row in set, 1 warning (0.01 sec)

    对此,我们可以做些优化:

    EXPLAIN SELECT * FROM city WHERE countrycode='CHN'
    UNION ALL
    SELECT * FROM city WHERE countrycode='USA';
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
    |  1 | PRIMARY     | city  | NULL       | ref  | CountryCode   | CountryCode | 3       | const |  363 |   100.00 | NULL  |
    |  2 | UNION       | city  | NULL       | ref  | CountryCode   | CountryCode | 3       | const |  274 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
    2 rows in set, 1 warning (0.00 sec)

    2.4 ref

    ref出现的条件是: 查找条件列使用了索引但不是PRIMARY KEYUNIQUE KEY。其意思就是虽然使用了索引,但该索引列的值并不唯一,有重复。这样即使使用索引快速查找到了第一条数据,仍然不能停止,要进行目标值附近的小范围扫描。但它的好处是它并不需要扫全表,因为索引是有序的,即便有重复值,也是在一个非常小的范围内扫描。

    EXPLAIN SELECT * FROM city WHERE countrycode='CHN';
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | city  | NULL       | ref  | CountryCode   | CountryCode | 3       | const |  363 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
    1 row in set, 1 warning (0.01 sec)

    2.6 eq_ref

    在多表连接时,连接条件(ON)使用了唯一索引(UNIQUE NOT NULL,PRIMARY KEY)时,走eq_ref

    -- 查询世界上人口小于100人的城市名字
    EXPLAIN SELECT city.name,city.population,country.code,country.name
    FROM city INNER JOIN country
    ON city.countrycode = country.code  -- country表的code字段是 pk
    WHERE city.population<100;
    +----+-------------+---------+------------+--------+---------------+---------+---------+------------------------+------+----------+-------------+
    | id | select_type | table   | partitions | type   | possible_keys | key     | key_len | ref                    | rows | filtered | Extra       |
    +----+-------------+---------+------------+--------+---------------+---------+---------+------------------------+------+----------+-------------+
    |  1 | SIMPLE      | city    | NULL       | ALL    | CountryCode   | NULL    | NULL    | NULL                   | 4188 |    33.33 | Using where |
    |  1 | SIMPLE      | country | NULL       | eq_ref | PRIMARY       | PRIMARY | 3       | world.city.CountryCode |    1 |   100.00 | NULL        |
    +----+-------------+---------+------------+--------+---------------+---------+---------+------------------------+------+----------+-------------+
    2 rows in set, 1 warning (0.00 sec)

    这里如果使用的单表来测试eq_ref,经常会出现const,后来想想是对的!UNIQUE NOT NULLPRIMARY KEY都具有唯一性,而匹配结果是唯一的可不就是const么!所以eq_ref多出现在联表查询中,因为联接条件通常都是具有唯一性或者主键!

    除了 system和 const类型,eq_ref是最好的联接类型。 

    2.7 const,system

    首先,system是最优的,它的意思是表只有一行(但效果我没演示出来),是const类型的一种特例,所以就把这两个列一块了

    EXPLAIN SELECT * FROM city WHERE id=3;
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | city  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
    1 row in set, 1 warning (0.00 sec)

    const表示:该表最多具有一个匹配行,该行在查询开始时读取。因为又只有一行,所以优化器的其余部分可以将这一行中的列的值视为一个常量。

    我们将const看作是最快的,因为只有一行匹配结果。

    三、key_len

    执行计划中的key_len字段计算的是使用到的索引的长度,通过ken_len可以帮助我们进一步确定索引的使用情况。
    这里只列出我们最常用的int、char、varchar三种数据类型,先来复习一些基础知识:

    • char和varchar是日常使用最多的字符类型。char(N)用于保存固定长度的字符串,长度最大为255,比指定长度大的值将被截短,而比指定长度小的值将会用空格进行填补。
    • varchar(N)用于保存可以变长的字符串,长度最大为65535,只存储字符串实际实际需要的长度(它会增加一个额外字节来存储字符串本身的长度),varchar使用额外的1~2字节来存储值的的长度,如果列的最大长度小于或者等于255,则用1字节,否则用2字节。

    char和varchar跟字符编码也有密切的联系,latin1占用1个字节,gbk占用2个字节,utf8占用3个字节。
    不同字符编码占用的存储空间不同:

    Latinl编码(一个字符1个字节):

    char(4)存储的字节varchar(4)存储的字节
    '' ' ' 4 '' 1
    'ab' 'ab ' 4 'ab' 3
    'abcd' 'abcd' 4 'abcd' 5
    'abcdefgh' 'abcd' 4 'abcd' 5

    GBK编码(一个字符2个字节):

    char(4)存储的字节varchar(4)存储的字节
    '' ' ' 8 '' 1
    'ab' 'ab ' 8 'ab' 5
    'abcd' 'abcd' 8 'abcd' 9
    'abcdefgh' 'abcd' 8 'abcd' 9

    UTF8编码(一个字符3个字节):

    char(4)存储的字节varchar(4)存储的字节
    '' ' ' 12 '' 1
    'ab' 'ab ' 12 'ab' 7
    'abcd' 'abcd' 12 'abcd' 13
    'abcdefgh' 'abcd' 12 'abcd' 13

    接下来,实验开始。
    有表结构如下:

    create table k1(
        id int not null primary key auto_increment,
        a int,
        b int not null,
        c char(10),
        d char(10) not null,
        e varchar(10),
        f varchar(10) not null
    )engine=innodb charset=utf8;

    我们定义的k1表,但没有添加数据,这没关系。

    首先来看int的索引长度怎么算出来的:

    -- 创建索引
    alter table k1 add index(a);
    alter table k1 add index(b);
    
    -- key_len的长度是5,对于字段a来说,int类型的索引本身占4个字节,且a字段又允许为空,所以再加1个字节的存储标志位,是否允许为空,加一起,正好是5个字节
    explain select * from k1 where a=1;
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | k1    | NULL       | ref  | a             | a    | 5       | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    
    -- 对于字段b来说,int类型的索引本身占4个字节,但a字段不允许为空,也就不用加1个字节的标志位了,所以ken_len是4
    explain select * from k1 where b=1;
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | k1    | NULL       | ref  | b             | b    | 4       | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    
    -- 再来看在联合索引中的使用情况
    alter table k1 add index mul_idx_a_b(a, b);
    explain select * from k1 where a=1 and b=1;  -- 使用的是联合索引,a是5字节,b是4字节,加一起是9个字节
    +----+-------------+-------+------------+------+---------------+---------+---------+-------------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key     | key_len | ref         | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+---------+---------+-------------+------+----------+-------+
    |  1 | SIMPLE      | k1    | NULL       | ref  | mul_idx_a_b,a,b   | mul_idx_a_b| 9       | const,const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+---------+---------+-------------+------+----------+-------+

    在来看char的索引长度怎么算出来的:

    -- 创建索引
    alter table k1 add index(c);
    alter table k1 add index(d);
    alter table k1 add index mul_idx_c_d(c, d);
    
    -- 字段c是char类型,1个字符用3个字节表示,char(10) * 3 = 30字节,且c字段又允许为空,所以再加1个字节的存储标志位,是否允许为空,加一起,正好是31个字节
    explain select * from k1 where c='a';
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | k1    | NULL       | ref  | c,mul_idx_c_d | c    | 31      | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    
    -- 字段d是char类型,1个字符用3个字节表示,char(10) * 3 = 30字节,但d字段不允许为空,也就不用加1个字节的标志位了,所以ken_len是30
    explain select * from k1 where d='a';
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | k1    | NULL       | ref  | d             | d    | 30      | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    
    -- 来看联合索引的使用情况,当然,首先要删除两个普通索引,因为MySQL对于下面的查询示例来说,会优先选择普通索引
    drop index c on k1;
    drop index d on k1;
    explain select * from k1 where c='a' and d='a';  -- c + d = 61
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key         | key_len | ref         | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------------+------+----------+-------+
    |  1 | SIMPLE      | k1    | NULL       | ref  | mul_idx_c_d   | mul_idx_c_d | 61      | const,const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------------+------+----------+-------+

    在来看varchar的索引长度怎么算出来的:

    -- 创建索引
    alter table k1 add index(e);
    alter table k1 add index(f);
    alter table k1 add index mul_idx_e_f(e, f);
    
    -- 字段e是varchar类型,varchar类型需要额外的1~2个字节存储字符本身的长度,这里取2。是否允许为空又占1个字节,所以是 10 * 3 + 2 + 1 = 33
    explain select * from k1 where e='a';
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | k1    | NULL       | ref  | e,mul_idx_e_f | e    | 33      | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    
    -- 字段f是varchar类型,但不允许为空,所以,10 * 3 + 2 = 32
    explain select * from k1 where f='a';
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref   | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    |  1 | SIMPLE      | k1    | NULL       | ref  | f             | f    | 32      | const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+------+---------+-------+------+----------+-------+
    
    -- 来看联合索引的使用情况,当然,首先要删除两个普通索引,因为MySQL对于下面的查询示例来说,会优先选择普通索引
    drop index e on k1;
    drop index f on k1;
    explain select * from k1 where e='a' and f='a';  -- 字段e的33 + 字段f的32 = 65
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------------+------+----------+-------+
    | id | select_type | table | partitions | type | possible_keys | key         | key_len | ref         | rows | filtered | Extra |
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------------+------+----------+-------+
    |  1 | SIMPLE      | k1    | NULL       | ref  | mul_idx_e_f   | mul_idx_e_f | 65      | const,const |    1 |   100.00 | NULL  |
    +----+-------------+-------+------------+------+---------------+-------------+---------+-------------+------+----------+-------+
  • 相关阅读:
    网址大全
    HTTPS的页面发送不了HTTP请求?——关于混合内容
    Prometheus node_exporter grafana部署安装
    seafile部署安装
    vnc服务器和windows2012密钥
    【转】缓存淘汰算法系列之3——FIFO类
    【转】缓存淘汰算法系列之2——LFU类
    【转】缓存淘汰算法系列之1——LRU类
    【转】缓存、缓存算法和缓存框架简介
    【转】Sizeof与Strlen的区别与联系
  • 原文地址:https://www.cnblogs.com/yj0405/p/14757870.html
Copyright © 2011-2022 走看看