Mysql索引 (二)

  • 1、索引进阶
  • 1.1、索引分类
  • 1.2、索引优化分析
  • 1.3、优化案例
  • 1.4、索引应用规范

  • 引入: 上一篇文章学习之旅8-mysql索引(一).我们简单的了解的什么是索引,索引执行(磁盘IO)以及索引的简单分类,接下来我们继续了解索引的分类,并通过分析执行计划来具体看看我们执行sql语句时是否使用索引,以及索引是否生效的一些情况。

1、索引进阶

1.1、索引分类

  • 上一篇文章我们知道在物理逻辑存储结构上,索引分为MyISAM索引和InnoDB索引(即聚集索引和辅助索引),接下来我们通过应用的层面来分类索引:

1)普通索引:mysql最基本的索引,一个表中可以有多个普通索引,允许空值。(index,key)
2)唯一索引:必须是唯一的列值,可以有多个唯一索引,允许为空值,对于InnoDB存储引擎没有主键的话,唯一键会被作为聚集索引。(unique key)
3)主键索引: 也叫主键,主键是唯一的,自动增长的字段一定是主键,但主键不一定自动增长,一般来说主键的列值意义不大(如序号),主键最好是数值类型,不允许空值,对于InnoDB存储引擎(mysql5.5后默认)会以主键作为聚集索引。(primary key)
4)复合索引:将表中多个列一起作为一个索引字段,此时需要注意的是必须遵守最佳左前缀原则,即列是顺序尤为重要(col1,col2,col3),若 where col2=B and col1=A或者where col2=B 将不走索引
5)前缀索引:当需要作为索引的列字段长度过长,导致索引树高度增加时,我们通常选择大字段前面的部分字符作为索引。char,varchar类型可以指定长度,blob,text类型必须指定,但是数值型不能指定。
6)全文索引:当字段是长文本时,若查询关键字用where col like '%xxx%'时,不满足最佳左前缀原则,索引失效,此时我们就需要建立全文索引(fulltext index)

1.2、索引优化分析

分析执行计划: 优化器(算法)最终得出的,代价最低的,SQL语句的执行方案。用于分析SQL的执行情况。通过explain 执行语句或者desc 执行语句来具体分析。以mysql中自带world数据库中数据为例:

mysql> desc select name,countrycode 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.03 sec)
mysql> explain select t1.name,countrycode from country t1 join city t2 on t1.code = t2.countrycode where t1.name='china';
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref           | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | PRIMARY       | NULL        | NULL    | NULL          |  239 |    10.00 | Using where |
|  1 | SIMPLE      | t2    | NULL       | ref  | CountryCode   | CountryCode | 3       | world.t1.Code |   18 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+

id:表的读取顺序,即select查询的序列号。id越大,优先级越高,越先执行。id相同则自上而下按顺序执行。
select_type:
1)SIMPLE:简单查询,select中不包括子查询或者union语句。
2)PRIMARY:select查询中包含任何复杂的子部分,最外层的查询则为PRIMARY。
3)SUBQUERY :在select或者where中包含了子查询
4)DERIVED :在from列表中包含的子查询被标记为DERIVED (衍生),后面对应的id指向对应table,即为该表的衍生表。mysql会递归执行这些子查询,把结果放在临时表中。
5)UNION:若第二个select出现在UNION之后,则被标记为UNION:若UNION包含在from子句的子查询中,外层select将被标记为:DERIVED。
6)UNION RESULT:UNION表获取结果的select。
table :当前执行对应的表
partitions :版本5.7以前,该项是explain partitions显示的选项,5.7以后成为了默认选项。该列显示的为分区表命中的分区情况。非分区表该字段为空(null)。
type:mysql中找到所需行的方式(访问类型),性能:system>const>eq_ref>ref>range>index>all
possible_keys :可能会使用到的索引(并不一定实际使用),查询的字段若存在索引,则会列出。
key :实际用到的索引,null则表示没有索引或者索引失效
key_len :查询用到的索引长度(字节数)。(数值越小说明精度越高)
如果是单列索引,那就整个索引长度算进去,如果是多列索引,那么查询不一定都能使用到所有的列,用多少算多少。
ref :ref表示使用了那些值进行索引查找,一般为一个常量值或表的某个字段。
rows :这是mysql估算的需要扫描的行数(不是精确值)。这个值非常直观显示 SQL 的效率好坏, 原则上 rows 越少越好
filtered :这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。
Extra :包含不适合在其他列中显示但十分重要的额外信息

针对以上explain的参数,并不是所有参数都重点关注的对象,下面我将选取相对重要的几个参数进行详细讲解:
首先我们看一下world数据库下面的city表和country表的结构,都是默认的InnoDB存储引擎。

mysql> desc city;
+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| ID          | int(11)  | NO   | PRI | NULL    | auto_increment |
| Name        | char(35) | NO   |     |         |                |
| CountryCode | char(3)  | NO   | MUL |         |                |
| District    | char(20) | NO   |     |         |                |
| Population  | int(11)  | NO   |     | 0       |                |
+-------------+----------+------+-----+---------+----------------+

mysql> desc country;
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
| Field          | Type                                                                                  | Null | Key | Default | Extra |
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
| Code           | char(3)                                                                               | NO   | PRI |         |       |
| Name           | char(52)                                                                              | NO   |     |         |       |
| Continent      | enum('Asia','Europe','North America','Africa','Oceania','Antarctica','South America') | NO   |     | Asia    |       |
| Region         | char(26)                                                                              | NO   |     |         |       |
| SurfaceArea    | float(10,2)                                                                           | NO   |     | 0.00    |       |
| IndepYear      | smallint(6)                                                                           | YES  |     | NULL    |       |
| Population     | int(11)                                                                               | NO   |     | 0       |       |
| LifeExpectancy | float(3,1)                                                                            | YES  |     | NULL    |       |
| GNP            | float(10,2)                                                                           | YES  |     | NULL    |       |
| GNPOld         | float(10,2)                                                                           | YES  |     | NULL    |       |
| LocalName      | char(45)                                                                              | NO   |     |         |       |
| GovernmentForm | char(45)                                                                              | NO   |     |         |       |
| HeadOfState    | char(60)                                                                              | YES  |     | NULL    |       |
| Capital        | int(11)                                                                               | YES  |     | NULL    |       |
| Code2          | char(2)                                                                               | NO   |     |         |       |
+----------------+---------------------------------------------------------------------------------------+------+-----+---------+-------+
15 rows in set (0.00 sec)

-- 由上可知,city表中有主键ID(聚集索引),country (普通索引)。country表中有主键Code(聚集索引)

1)type:mysql中找到所需行的方式(访问类型),性能:system>const>eq_ref>ref>range>index>all

  • all:全表扫描,条件无索引,需要遍历全表才能找到匹配的行。
mysql> desc select * from city;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| 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 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.01 sec)

mysql> desc select * from city where name not like '%CH%';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 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 |    88.89 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> desc 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.01 sec)
  • index :全索引扫描,需要遍历索引树(即查询的列有索引),没有where条件的查询。(id 和country分别是主键,普通索引列)
mysql> desc 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 |
+----+-------------+-------+------------+-------+---------------+-------------+---------+------+------+----------+-------------+

mysql> desc select countrycode 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.01 sec)
  • range:索引范围查询,通常指模糊查询等,where语句中有> 、< 、>= 、<=、 like、 between and、or、in等。但是这里注意,若where条件后面的列为整型,浮点型时,不能用like做模糊查询,不然此时将全表扫描,不走任何索引,且用like做模糊查询必须满足最佳左前缀原则,不然也不走索引。
-- ID 为int型,不能用like做模糊查询
mysql> desc select * from city where ID  in( '100');
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 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)

mysql> desc select * from city where ID  in( 100,200);
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 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)

mysql> desc select * from city where ID =100 or id = 200;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 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)

-- like做模糊查询必须满足最佳左前缀原则,不然也不走索引
mysql> desc select * from country where code  like 'US%';
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | country | NULL       | range | PRIMARY       | PRIMARY | 3       | NULL |    1 |   100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)

mysql> desc select * from country where code  like '%US%';
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | country | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  239 |    11.11 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

但是对于> 、< 、>= 、<=、 like、 between and查询的性能要优于or、in,因为在叶子节点存放的数据是有序排列的,对于B+树来说,每个叶子节点之间是相互连通的,这样就加快了访问速度。而对于or、in 这种连续的查询仅仅相当于B树,需要多次从根节点到叶子节点,查询效率就降低了。对于这种情况我们一般通过union all 来进一步优化查询。

-- type 从范围查询变成了聚集索引等值查询。const > range
mysql> desc select * from city where ID =100 or id = 200;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 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)

mysql> desc select * from city where ID =100 union all select * from city where id = 200;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | PRIMARY     | city  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
|  2 | UNION       | city  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
2 rows in set, 1 warning (0.01 sec)

注意:当对应表中的主键列查询时除外(city表中主键为ID,country表中code为主键),类型为:range

mysql> desc select * from city where ID  not in( '1000','200');
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | city  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL | 3092 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select * from country where code in( 'CHN','USA');
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | country | NULL       | range | PRIMARY       | PRIMARY | 3       | NULL |    2 |   100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
  • ref:辅助索引等值查询,返回匹配某个单独值的所有行(可能有多个值)
mysql> desc 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.00 sec)
  • eq_ref:多表连接中,非驱动表(子表)连接条件是主键或者唯一键,对于每个索引键,返回值唯一。
    即唯一性索引扫描,
-- 对应city 表中有很多countrycode 与 country表中对应,不只一条数据,此时type类型为ref
mysql> desc select t2.name from city t1 ,country t2 where t1.countrycode=t2.code;
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref           | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
|  1 | SIMPLE      | t2    | NULL       | ALL  | PRIMARY       | NULL        | NULL    | NULL          |  239 |   100.00 | NULL        |
|  1 | SIMPLE      | t1    | NULL       | ref  | CountryCode   | CountryCode | 3       | world.t2.Code |   18 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
-- 同理 t2表(country表)虽然只有一个叫china的国家,但是查询出的结果仍然是多行数据,where过滤条件中没有限定唯一的数据行,因此也只能是eef。
mysql> desc select t1.name from city t1 ,country t2 where t1.countrycode=t2.code and t2.name='china';
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref           | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
|  1 | SIMPLE      | t2    | NULL       | ALL  | PRIMARY       | NULL        | NULL    | NULL          |  239 |    10.00 | Using where |
|  1 | SIMPLE      | t1    | NULL       | ref  | CountryCode   | CountryCode | 3       | world.t2.Code |   18 |   100.00 | NULL        |
+----+-------------+-------+------------+------+---------------+-------------+---------+---------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

-- 而下面这种情况where条件中过滤了city表中叫 chengdu 的城市,只有唯一的一行数据,因此是eq_ref。
mysql> desc select t1.name from city t1 ,country t2 where t1.countrycode=t2.code and t1.name='chengdu';
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------------+------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref                  | rows | filtered | Extra       |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------------+------+----------+-------------+
|  1 | SIMPLE      | t1    | NULL       | ALL    | CountryCode   | NULL    | NULL    | NULL                 | 4188 |    10.00 | Using where |
|  1 | SIMPLE      | t2    | NULL       | eq_ref | PRIMARY       | PRIMARY | 3       | world.t1.CountryCode |    1 |   100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+---------+---------+----------------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
  • const:主键或者唯一键等值查询。表最多有一个匹配行,它将在查询开始时被读取。因为仅有一行,在这行的列值可被优化器剩余部分认为是常数。const表很快,因为它们只读取一次!
mysql> desc select * from city where id=2999;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 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)
  • key_len: 联合索引覆盖长度(一般来说主要看联合索引)。对于联合索引index(a,b,c),希望对于联合索引应用更加充分。用key_len可以帮助我们查询走了索引的几部分(全覆盖,部分覆盖,不覆盖)
    关于长度:只列的储存字节的最大长度(计算最大预留长度 字节byte),数据的类型以及字符集会影响长度。utf-8来说,一个字符最大占3个字节,而utf-8mb4(支持emoji)一个字符最大占4个字节。另外对于not null 不需要单独的字节,而没有定义(无not null)的字段需要一个字节来单独储存。同理对于varchar类型也需要单独的1-2个字节来储存字节长度这个数,比如varchar(10) ,10这个数字就需要单独字节来储存。
    eg:index(a,b,c,d)
    全覆盖:
    select * from tb1 where a= and b= and c= and d= ;
    select * from tb1 where a in and b in and c= and d in ;
    select * from tb1 where b= and c= and d= and a= ;
    部分覆盖:
    select * from tb1 where a= ;
    select * from tb1 where a= and c= ;
    select * from tb1 where b= and b= order by c;
    不覆盖:
    select * from b= and c= ;
    select * from d= ;

索引键 索引键值按升序排列_索引键

mysql> create table tb1(
    -> a int not null,               ------->4字节
    -> b int,			             ------->4+1字节
    -> c char(10) not null,          ------->10*3字节
    -> d varchar(10)                 ------->10*3+2+1字节
    -> )  charset=utf8;              --------4+5+30+33=72
Query OK, 0 rows affected (0.03 sec)

mysql> create index id_abcd on tb1(a,b,c,d);

mysql> desc select * from tb1 where a=1 and b=1 and c='c' and d='d';
+----+-------------+-------+------------+------+---------------+---------+---------+-------------------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key     | key_len | ref                     | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+---------+---------+-------------------------+------+----------+-------------+
|  1 | SIMPLE      | tb1   | NULL       | ref  | id_abcd       | id_abcd | 72      | const,const,const,const |    1 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+---------+---------+-------------------------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
--由上可知,我们在a,b,c,d字段上建立了一个联合索引idx_abcd,当我们查询条件使用到联合索引时(满足最佳最前缀原则)。
--全覆盖:
a=1 and b=1 and c='c' and d='d' 用到4个(4+5+30+33=72)此时索引覆盖长度为72(所有字段都用到);
--key_len 只指示了WHERE中用于条件过滤时被选中的索引列,是不包含 ORDER BY/GROUP BY 这部分被选中的索引列。
mysql> desc select * from tb1 where a=1 and b=2 order by  c;
+----+-------------+-------+------------+------+---------------+---------+---------+-------------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key     | key_len | ref         | rows | filtered | Extra                    |
+----+-------------+-------+------------+------+---------------+---------+---------+-------------+------+----------+--------------------------+
|  1 | SIMPLE      | tb1   | NULL       | ref  | id_abcd       | id_abcd | 9       | const,const |    1 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+------+---------------+---------+---------+-------------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select * from tb1 where a=1 and b=2 and c='c' order by  d;
+----+-------------+-------+------------+------+---------------+---------+---------+-------------------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key     | key_len | ref               | rows | filtered | Extra                    |
+----+-------------+-------+------------+------+---------------+---------+---------+-------------------+------+----------+--------------------------+
|  1 | SIMPLE      | tb1   | NULL       | ref  | id_abcd       | id_abcd | 39      | const,const,const |    1 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+------+---------------+---------+---------+-------------------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
--部分覆盖:
a=1 and b=2  用到2个(4+5=9)
a=1 and b=2 and c='c' 用到3个(4+5+30=39)

mysql> desc select * from tb1 where b=2 and c='c' order by  d;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                                    |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+------------------------------------------+
|  1 | SIMPLE      | tb1   | NULL       | index | NULL          | id_abcd | 72      | NULL |    1 |   100.00 | Using where; Using index; Using filesort |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+------------------------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> desc select * from tb1 where b=2;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | tb1   | NULL       | index | NULL          | id_abcd | 72      | NULL |    1 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)
--以上不覆盖:
  • Extra:一些额外的比较重要的信息。

Using filesort:说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取(order by ,group by等)。MySQL中无法利用索引。

mysql> desc city;
+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |
+-------------+----------+------+-----+---------+----------------+
| ID          | int(11)  | NO   | PRI | NULL    | auto_increment |
| Name        | char(35) | NO   |     |         |                |
| CountryCode | char(3)  | NO   | MUL |         |                |
| District    | char(20) | NO   |     |         |                |
| Population  | int(11)  | NO   | MUL | 0       |                |
+-------------+----------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
-- 在city表中CountryCode 和 Population上都有单独的普通索引。对于以下查询就会出现Using filesort,因为where条件和order by条件没有联合索引,排序不会按照两者同时进行,因此需要建立联合索引: create index id_cp on city(countrycode,population);
mysql> desc select * from city where countrycode='chn' order by population;
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+---------------------------------------+
| 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 | Using index condition; Using filesort |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+---------------------------------------+
1 row in set, 1 warning (0.00 sec)

--创建联合索引,Using filesort消失
mysql> create index id_cp on city(countrycode,population);
mysql> desc select * from city where countrycode='chn' order by population;
+----+-------------+-------+------------+------+-------------------+-------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys     | key   | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-------+------------+------+-------------------+-------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | city  | NULL       | ref  | CountryCode,id_cp | id_cp | 3       | const |  363 |   100.00 | Using index condition |
+----+-------------+-------+------------+------+-------------------+-------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

1.3、优化案例

  • 通过什么语句、软件来进行优化?具体如何优化?
    1、mysql出现性能问题,突然变得很慢:
    1)首先查询用户连接情况,一般来说都是用户进行查询时出现这种问题,show processlist,获取到导致数据库卡住的语句。
    2)获取到该语句后,用explain或desc对该语句进行分析,查看索引情况。
    3)进一步建立或者优化索引。

2、一段时间很慢(持续性)
1)查看关于慢日志情况(slowlog),分析slowlog
2)用explain或desc进行分析,查看索引情况。
3)进一步建立或者优化索引。

1.4、索引应用规范

  • 索引建立的原则:
    1)一般来说对于建表时应该设置主键,一般来说是无关的列(自增);
    2)经常作为where条件的列,order by ,group by ,join on ,distinct 的条件
    3)最好使用唯一值对的列作为联合索引的前导列,其他按照联合索引优化细节来处理;
    4)对于列值过长的,建议建立前缀索引,指定长度;
    5)降低索引数量,对于不常用的列不要建立索引,通过删除不需要的索引来进行优化处理(percona toolkit)或者sqlyog工具中的信息查看索引情况优化;
    6)维护索引比例业务繁忙期;
    7)小表一般不建立索引
  • 不走索引情况:
    1)没有查询条件(select * from t1)或者查询条件没有建立索引;
    2)查询结果集是原表中的大部分数据,应该是25%以上(数据量过大也有可能不会走索引);
    3)索引本身失效,统计数据不真实;
    同一个语句原来走索引,但是某时间突然很慢–可能是统计信息过旧导致索引失效
    4)查询条件使用函数在索引列上,或者对索引列进行运算操作(+,-,* ,/,!等)
    5)对于隐式转换导致索引失效:比例定义的字段tel varchar(11),我们在查询时可以用where =‘110’ 或者 where = 110 ,虽然查询结果一样,但是对于查询的性能却不一样,因为我们定义的tel 为字符类型,我们在该字段建立索引后,若查询条件where = 110,此时mysql会自动将110 转换 ‘110’,此时我们对索引项做了函数处理,故索引失效。
    where = ‘110’ 走ref , 而where = 110 则为 all;
    6)not in 、!= 、<> 不走索引 or、in尽量转换为union
    7)满足最佳左前缀原则’xx%’,而’%xx%'不走索引。

参考资料: MySQL 5.7参考手册