MySQL高级-索引的使用及优化

索引的使用

  • 1 验证索引提升查询效率
  • 2 索引的使用
    • 2.1 准备环境
    • 2.2 避免索引失效
      • 1). 全值匹配 ,对索引中所有列都指定具体值。
      • 2). 最左前缀法则(复合索引)
      • 3). 范围查询右边的列,不能使用索引
      • 4). 不要在索引列上进行运算操作, 索引将失效
      • 5). 字符串不加单引号,造成索引失效
      • 6). 尽量使用覆盖索引,避免select *
      • 7). 用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到
      • 8). 以%开头的Like模糊查询,索引失效
      • 9). 如果MySQL评估使用索引比全表更慢,则不使用索引
      • 10). is NULL , is NOT NULL 有时索引失效
      • 11). in 走索引, not in 索引失效
      • 12). 单列索引和复合索引
  • 3 查看索引使用情况

关于MySQL索引一些具体特性在 MySQL索引中有具体的介绍,这里主要介绍索引的使用
索引是数据库优化最常用也是最重要的手段之一, 通过索引通常可以帮助用户解决大多数的MySQL的性能优化问题。

1 验证索引提升查询效率

在我们准备的表结构tb_item 中, 一共约存储了250万记录;具体的创建过程参见SQL优化步骤(explain等)

查看tb_item表中元素的个数:

mysql> select count(*) from tb_item;
+----------+
| count(*) |
+----------+
|  2499695 |
+----------+
1 row in set (2.60 sec)

使用id字段和name字段进行精确查询。

mysql> select * from tb_item where id=1800;
+------+---------------+----------+-------+------------+--------+------------+---------------------+---------------------+
| id   | title         | price    | num   | categoryid | status | sellerid   | createtime          | updatetime          |
+------+---------------+----------+-------+------------+--------+------------+---------------------+---------------------+
| 1800 | 货物1800| 63271.23 | 56516 |          9 | 1      | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
+------+---------------+----------+-------+------------+--------+------------+---------------------+---------------------+
1 row in set (0.00 sec)

mysql> select * from tb_item where title='货物1000号';
+------+---------------+---------+-------+------------+--------+------------+---------------------+---------------------+
| id   | title         | price   | num   | categoryid | status | sellerid   | createtime          | updatetime          |
+------+---------------+---------+-------+------------+--------+------------+---------------------+---------------------+
| 1000 | 货物1000| 6610.28 | 95953 |          5 | 1      | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
+------+---------------+---------+-------+------------+--------+------------+---------------------+---------------------+
1 row in set (3.68 sec)

我们可以看到,使用id字段精确查询数据非常快,0秒就完成,而是用title字段精确查询数据需要3.68秒。因为id字段是有索引的,因为在数据表中,只要某个字段被定义了主键,那么这个字段就具有主键索引。

处理方案 , 针对title字段, 创建索引 :

create index idx_item_title on tb_item(title);

我们发现。创建索引的时间也需要51s。
索引创建完成之后,再次进行查询 :

mysql> select * from tb_item where title='货物1000号';
+------+---------------+---------+-------+------------+--------+------------+---------------------+---------------------+
| id   | title         | price   | num   | categoryid | status | sellerid   | createtime          | updatetime          |
+------+---------------+---------+-------+------------+--------+------------+---------------------+---------------------+
| 1000 | 货物1000| 6610.28 | 95953 |          5 | 1      | 5435343235 | 2019-04-20 22:37:15 | 2019-04-20 22:37:15 |
+------+---------------+---------+-------+------------+--------+------------+---------------------+---------------------+
1 row in set (0.07 sec)

此时执行时间仅0.07s。这样也就验证了索引对查询效率的提升。

2 索引的使用

创建了索引也不一定会极大的提高效率,只有好好的利用了索引,才会改善效率。

2.1 准备环境

create table `tb_seller` (
	`sellerid` varchar (100),
	`name` varchar (100),
	`nickname` varchar (50),
	`password` varchar (60),
	`status` varchar (1),
	`address` varchar (100),
	`createtime` datetime,
    primary key(`sellerid`)
)engine=innodb default charset=utf8mb4; 

insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('alibaba','阿里巴巴','阿里小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('baidu','百度科技有限公司','百度小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('huawei','华为科技有限公司','华为小店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itcast','传智播客教育科技有限公司','传智播客','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('itheima','黑马程序员','黑马程序员','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('luoji','罗技科技有限公司','罗技小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('oppo','OPPO科技有限公司','OPPO官方旗舰店','e10adc3949ba59abbe56e057f20f883e','0','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('ourpalm','掌趣科技股份有限公司','掌趣小店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('qiandu','千度科技','千度小店','e10adc3949ba59abbe56e057f20f883e','2','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('sina','新浪科技有限公司','新浪官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('xiaomi','小米科技','小米官方旗舰店','e10adc3949ba59abbe56e057f20f883e','1','西安市','2088-01-01 12:00:00');
insert into `tb_seller` (`sellerid`, `name`, `nickname`, `password`, `status`, `address`, `createtime`) values('yijia','宜家家居','宜家家居旗舰店','e10adc3949ba59abbe56e057f20f883e','1','北京市','2088-01-01 12:00:00');


create index idx_seller_name_sta_addr on tb_seller(name,status,address);

注意:这里对name,status,address创建了联合索引。

mysql> select * from tb_seller;
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| sellerid | name                                 | nickname              | password                         | status | address   | createtime          |
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| alibaba  | 阿里巴巴                             | 阿里小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| baidu    | 百度科技有限公司                     | 百度小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| huawei   | 华为科技有限公司                     | 华为小店              | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| itcast   | 传智播客教育科技有限公司             | 传智播客              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| itheima  | 黑马程序员                           | 黑马程序员            | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| luoji    | 罗技科技有限公司                     | 罗技小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| oppo     | OPPO科技有限公司                     | OPPO官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| ourpalm  | 掌趣科技股份有限公司                 | 掌趣小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| qiandu   | 千度科技                             | 千度小店              | e10adc3949ba59abbe56e057f20f883e | 2      | 北京市    | 2088-01-01 12:00:00 |
| sina     | 新浪科技有限公司                     | 新浪官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| xiaomi   | 小米科技                             | 小米官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 西安市    | 2088-01-01 12:00:00 |
| yijia    | 宜家家居                             | 宜家家居旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
12 rows in set (0.00 sec)

2.2 避免索引失效

1). 全值匹配 ,对索引中所有列都指定具体值。

改情况下,索引生效,执行效率高。

例如:

mysql> select * from tb_seller where name ='小米科技' and status='1' and address='北京市';
Empty set (0.01 sec)

索引生效

mysql> explain select * from tb_seller where name ='小米科技' and status='1' and address='北京市';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------------+------+----------+-------+
| id | select_type | table     | partitions | type | possible_keys            | key                      | key_len | ref               | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 813     | const,const,const |    1 |   100.00 | NULL  |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

explain的各个参数的具体解释可以参考文章SQL优化步骤,这里的simple表示的是简单的select查询,查询中不包含子查询和union查询,type中的ref表示非唯一性索引扫描,索引用的是idx_seller_name_sta_addr,ref中的const,const,const表示我们
是按照常量查询。

2). 最左前缀法则(复合索引)

如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
我们创建了符合索引name,status,address.比如我们在进行查询的时候,需要从最左边的列开始,即查询的条件中必须包含最左边的列name,并且不能跳过索引当中的列。
我们以下面的案例来演示一下

匹配最左前缀法则,走索引:
只查询最左边的列name,可以看到走了索引,key的内容idx_seller_name_sta_addr,key_len为403

mysql> explain select * from tb_seller where name ='小米科技';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
| id | select_type | table     | partitions | type | possible_keys            | key                      | key_len | ref   | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 403     | const |    1 |   100.00 | NULL  |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.03 sec)

查询左边的两个name和status,也走索引,索引的长度变为410

mysql> explain select * from tb_seller where name ='小米科技' and status='1';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
| id | select_type | table     | partitions | type | possible_keys            | key                      | key_len | ref         | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 410     | const,const |    1 |   100.00 | NULL  |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

违法最左前缀法则 , 索引失效:
如果我们跳过了name,查询status和address,就不走索引了

mysql> explain select * from tb_seller where status='1' and address='北京市';
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   12 |     8.33 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

如果三者的顺序发生变化,也是走索引的。

mysql> explain select * from tb_seller where status='1' and address='北京市' and name='小米科技';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------------+------+----------+-------+
| id | select_type | table     | partitions | type | possible_keys            | key                      | key_len | ref               | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 813     | const,const,const |    1 |   100.00 | NULL  |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

只要我们查询条件中包含最左边的列,并且没有跳跃,这个时候就会走索引。
有疑问的地方在于,跳过了中间的status,也走了索引。我们可以看到索引长度为403,说明只走了name字段的索引。但是address的索引并没有走。
如果符合最左法则,但是出现跳跃某一列,只有最左列索引生效:

mysql> explain select * from tb_seller where name='小米科技' and address='北京市';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table     | partitions | type | possible_keys            | key                      | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 403     | const |    1 |    10.00 | Using index condition |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

3). 范围查询右边的列,不能使用索引

这里我们先注意一下, 由上面的分析,走1列、2列、3列索引时,索引的长度分别为403,410,413。
status用到了范围查询:

mysql> explain select * from tb_seller where name='小米科技' and status > '1' and address='北京市';
+----+-------------+-----------+------------+-------+--------------------------+--------------------------+---------+------+------+----------+-----------------------+
| id | select_type | table     | partitions | type  | possible_keys            | key                      | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-----------+------------+-------+--------------------------+--------------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_seller | NULL       | range | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 410     | NULL |    1 |    10.00 | Using index condition |
+----+-------------+-----------+------------+-------+--------------------------+--------------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

根据前面的两个字段name , status 查询是走索引的, 但是最后一个条件address 没有用到索引。

4). 不要在索引列上进行运算操作, 索引将失效

注意:以下例子中,substring为取子串,其中字符串从1开始

mysql> select * from tb_seller where substring(name,3,2)='科技';
+----------+--------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| sellerid | name                           | nickname              | password                         | status | address   | createtime          |
+----------+--------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| baidu    | 百度科技有限公司               | 百度小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| huawei   | 华为科技有限公司               | 华为小店              | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| luoji    | 罗技科技有限公司               | 罗技小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| ourpalm  | 掌趣科技股份有限公司           | 掌趣小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| qiandu   | 千度科技                       | 千度小店              | e10adc3949ba59abbe56e057f20f883e | 2      | 北京市    | 2088-01-01 12:00:00 |
| sina     | 新浪科技有限公司               | 新浪官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| xiaomi   | 小米科技                       | 小米官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 西安市    | 2088-01-01 12:00:00 |
+----------+--------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
7 rows in set (0.00 sec)

mysql> explain select * from tb_seller where substring(name,3,2)='科技';
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   12 |   100.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

5). 字符串不加单引号,造成索引失效

mysql> explain select * from tb_seller where name='小米科技' and status =1;
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table     | partitions | type | possible_keys            | key                      | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 403     | const |    1 |    10.00 | Using index condition |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-----------------------+
1 row in set, 2 warnings (0.00 sec)

mysql> explain select * from tb_seller where name='小米科技' and status ='1';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
| id | select_type | table     | partitions | type | possible_keys            | key                      | key_len | ref         | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 410     | const,const |    1 |   100.00 | NULL  |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

看来没有加单引号也是有影响的,因为MySQL底层检测到status是varchar类型,它就会对这一部分的值进行隐式类型转换,之后这个索引字段就失效了。

6). 尽量使用覆盖索引,避免select *

尽量使用覆盖索引(只访问索引的查询(索引列完全包含查询列)),减少select * 。

mysql> explain select name,status,address from tb_seller where name='小米科技';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+-     -----+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys            | key                      | key_len | ref   |      rows | filtered | Extra       |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+-     -----+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 403     | const |         1 |   100.00 | Using index |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+-     -----+----------+-------------+
1 row in set, 1 warning (0.00 sec)

这里extra字段为using index,不需要回表查询了。
如果查询列,超出索引列,也会降低性能。
TIP :

using index :使用覆盖索引的时候就会出现

using where:在查找使用索引的情况下,需要回表去查询所需的数据

using index condition:查找使用了索引,但是需要回表查询数据

using index ; using where:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据

7). 用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到

or前的条件中的列有索引,后面的列中没有索引。那么索引失效。

mysql> explain select * from tb_seller where name='小米科技' or nickname='小米官方旗舰店';
+----+-------------+-----------+------------+------+--------------------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys            | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+--------------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ALL  | idx_seller_name_sta_addr | NULL | NULL    | NULL |   12 |    19.00 | Using where |
+----+-------------+-----------+------------+------+--------------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

如果换成and呢,可见and是可以的。

mysql> explain select * from tb_seller where name='小米科技' and nickname='小米官方旗舰店';
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys            | key                      | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 403     | const |    1 |    10.00 | Using where |
+----+-------------+-----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

8). 以%开头的Like模糊查询,索引失效

如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
查询以科技开头的字符串。此时百分号在尾部,索引没有失效。

mysql> explain select * from tb_seller where name like '科技%';
+----+-------------+-----------+------------+-------+--------------------------+--------------------------+---------+------+------+----------+-----------------------+
| id | select_type | table     | partitions | type  | possible_keys            | key                      | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-----------+------------+-------+--------------------------+--------------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_seller | NULL       | range | idx_seller_name_sta_addr | idx_seller_name_sta_addr | 403     | NULL |    1 |   100.00 | Using index condition |
+----+-------------+-----------+------------+-------+--------------------------+--------------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

查询以科技结尾的字符串,此时百分号开头,索引失效。

mysql> explain select * from tb_seller where name like '%科技';
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   12 |    11.11 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

解决方案 :
通过覆盖索引来解决

在前面已经介绍,覆盖索引指的是索引列完全包含查询列,只查询包含索引的列。

如下面的例子,sellerid和name都建立了索引,所以使用%开头的模糊查询一样走索引。

mysql> explain select sellerid,name from tb_seller where name like "%科技";
+----+-------------+-----------+------------+-------+---------------+--------------------------+---------+------+------+----------+--------------------------+
| id | select_type | table     | partitions | type  | possible_keys | key                      | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-----------+------------+-------+---------------+--------------------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | tb_seller | NULL       | index | NULL          | idx_seller_name_sta_addr | 813     | NULL |   12 |    11.11 | Using where; Using index |
+----+-------------+-----------+------------+-------+---------------+--------------------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

9). 如果MySQL评估使用索引比全表更慢,则不使用索引

根据前面的,我们在tb_seller表中建立了主键索引sellerid,和复合索引(name,status,address)

mysql> explain select * from tb_seller where address='北京市';
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   12 |    10.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

根据最左前缀法则,上面的语句索引失效。
要想让上面的语句在执行的时候使用索引,我们给address列建立一个单列索引

mysql> create index idx_seller_address on tb_seller(address);
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> explain select * from tb_seller where address='北京市';
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys      | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ALL  | idx_seller_address | NULL | NULL    | NULL |   12 |    91.67 | Using where |
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from tb_seller where address='西安市';
+----+-------------+-----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
| id | select_type | table     | partitions | type | possible_keys      | key                | key_len | ref   | rows | filtered | Extra |
+----+-------------+-----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_address | idx_seller_address | 403     | const |    1 |   100.00 | NULL  |
+----+-------------+-----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

但是在查询北京市的时候,实际没有走索引,在查询西安市的时候,实际竟走了索引。

mysql> select * from tb_seller;
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| sellerid | name                                 | nickname              | password                         | status | address   | createtime          |
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
| alibaba  | 阿里巴巴                             | 阿里小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| baidu    | 百度科技有限公司                     | 百度小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| huawei   | 华为科技有限公司                     | 华为小店              | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| itcast   | 传智播客教育科技有限公司             | 传智播客              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| itheima  | 黑马程序员                           | 黑马程序员            | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| luoji    | 罗技科技有限公司                     | 罗技小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| oppo     | OPPO科技有限公司                     | OPPO官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 0      | 北京市    | 2088-01-01 12:00:00 |
| ourpalm  | 掌趣科技股份有限公司                 | 掌趣小店              | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| qiandu   | 千度科技                             | 千度小店              | e10adc3949ba59abbe56e057f20f883e | 2      | 北京市    | 2088-01-01 12:00:00 |
| sina     | 新浪科技有限公司                     | 新浪官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
| xiaomi   | 小米科技                             | 小米官方旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 西安市    | 2088-01-01 12:00:00 |
| yijia    | 宜家家居                             | 宜家家居旗舰店        | e10adc3949ba59abbe56e057f20f883e | 1      | 北京市    | 2088-01-01 12:00:00 |
+----------+--------------------------------------+-----------------------+----------------------------------+--------+-----------+---------------------+
12 rows in set (0.00 sec)

我们发现12条记录,有11条都是北京市,而只有一条是西安市,MySQL底层解析器在执行计划的时候,12条记录11条是北京市,那还不如全表扫描来的更快。
如果数据量比较大,而某一条数据占用的比例特别大,基本上覆盖了所有的比例,这时候就不会再走索引了,而走全表扫描。

10). is NULL , is NOT NULL 有时索引失效

此时我们已经为address字段创建了单列索引,操作的时候我们以address为例。

mysql> explain select * from tb_seller where address is null;
+----+-------------+-----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table     | partitions | type | possible_keys      | key                | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+-----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_address | idx_seller_address | 403     | const |    1 |   100.00 | Using index condition |
+----+-------------+-----------+------------+------+--------------------+--------------------+---------+-------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from tb_seller where address is not null;
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys      | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ALL  | idx_seller_address | NULL | NULL    | NULL |   12 |   100.00 | Using where |
+----+-------------+-----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

is null走了索引,is not null没有走索引,而我们tb_seller表中,所有的值都不为空,说明值为空是少量的数据(也包括无),此时就走索引,而is not null走索引。
因为MySQL底层会自动判断,使用索引有没有必要。

11). in 走索引, not in 索引失效

mysql> explain select * from tb_seller where sellerid in('oppo','xiaomi','sina');
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | range | PRIMARY       | PRIMARY | 402     | NULL |    3 |   100.00 | Using where |
+----+-------------+-----------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from tb_seller where sellerid not in('oppo','xiaomi','sina');
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ALL  | PRIMARY       | NULL | NULL    | NULL |   12 |    83.33 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.01 sec)

12). 单列索引和复合索引

尽量使用复合索引,而少使用单列索引

创建复合索引

create index idx_name_sta_address on tb_seller(name, status, address);

就相当于创建了三个索引 : 
	name
	name + status
	name + status + address

创建单列索引

create index idx_seller_name on tb_seller(name);
create index idx_seller_status on tb_seller(status);
create index idx_seller_address on tb_seller(address);

数据库会选择一个最优的索引(辨识度最高索引)来使用,并不会使用全部索引 。

我们先删去原来的复合索引,然后再给原来的字段添加三个单列索引

mysql> explain select * from tb_seller where name='小米科技' and status='0' and address='西安市';
+----+-------------+-----------+------------+------+---------------------------------------------+--------------------------+---------+-------------------+------+----------+-------+
| id | select_type | table     | partitions | type | possible_keys                               | key                      | key_len | ref               | rows | filtered | Extra |
+----+-------------+-----------+------------+------+---------------------------------------------+--------------------------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_name_sta_addr,idx_seller_address | idx_seller_name_sta_addr | 813     | const,const,const |    1 |   100.00 | NULL  |
+----+-------------+-----------+------------+------+---------------------------------------------+--------------------------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> drop index idx_seller_name_sta_addr on tb_seller;
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> create index idx_seller_name on tb_seller(name);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> create index idx_seller_status on tb_seller(status);
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> create index idx_seller_address on tb_seller(address);
ERROR 1061 (42000): Duplicate key name 'idx_seller_address'

mysql> show index from tb_seller;
+-----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table     | Non_unique | Key_name           | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| tb_seller |          0 | PRIMARY            |            1 | sellerid    | A         |          11 |     NULL | NULL   |      | BTREE      |         |               |
| tb_seller |          1 | idx_seller_address |            1 | address     | A         |           2 |     NULL | NULL   | YES  | BTREE      |         |               |
| tb_seller |          1 | idx_seller_name    |            1 | name        | A         |          12 |     NULL | NULL   | YES  | BTREE      |         |               |
| tb_seller |          1 | idx_seller_status  |            1 | status      | A         |           3 |     NULL | NULL   | YES  | BTREE      |         |               |
+-----------+------------+--------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (0.01 sec)

我们再来执行同样的语句

mysql>  explain select * from tb_seller where name='小米科技' and status='0' and address='西安市';
+----+-------------+-----------+------------+------+------------------------------------------------------+--------------------+---------+-------+------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys                                        | key                | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-----------+------------+------+------------------------------------------------------+--------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | tb_seller | NULL       | ref  | idx_seller_address,idx_seller_name,idx_seller_status | idx_seller_address | 403     | const |    1 |     8.33 | Using where |
+----+-------------+-----------+------------+------+------------------------------------------------------+--------------------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

三个单列索引都有可能会用到,但实际上只用到了name这个字段的索引。因为数据库会选择一个最优的索引(辨识度最高索引)来使用,并不会使用全部索引 。这里面name字段辨识度最高,因为小米科技这个值只出现了一次,在tb_seller表中。

3 查看索引使用情况

show status like 'Handler_read%';	

show global status like 'Handler_read%';	
mysql> show status like 'Handler_read%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_first    | 8     |
| Handler_read_key      | 10    |
| Handler_read_last     | 0     |
| Handler_read_next     | 13    |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 117   |
+-----------------------+-------+
7 rows in set (0.03 sec)

mysql> show global status like 'Handler_read%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_first    | 16    |
| Handler_read_key      | 16    |
| Handler_read_last     | 0     |
| Handler_read_next     | 15    |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 859   |
+-----------------------+-------+
7 rows in set (0.01 sec)
Handler_read_first:索引中第一条被读的次数。如果较高,表示服务器正执行大量全索引扫描(这个值越低越好)。

Handler_read_key:如果索引正在工作,这个值代表一个行被索引值读的次数,如果值越低,表示索引得到的性能改善不高,因为索引不经常使用(这个值越高越好)。

Handler_read_next :按照键顺序读下一行的请求数。如果你用范围约束或如果执行索引扫描来查询索引列,该值增加。

Handler_read_prev:按照键顺序读前一行的请求数。该读方法主要用于优化ORDER BY ... DESC。

Handler_read_rnd :根据固定位置读一行的请求数。如果你正执行大量查询并需要对结果进行排序该值较高。你可能使用了大量需要MySQL扫描整个表的查询或你的连接没有正确使用键。这个值较高,意味着运行效率低,应该建立索引来补救。

Handler_read_rnd_next:在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明你的表索引不正确或写入的查询没有利用索引。

你可能感兴趣的:(MySQL,数据库,数据库,mysql)