在我们准备的表结构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。这样也就验证了索引对查询效率的提升。
创建了索引也不一定会极大的提高效率,只有好好的利用了索引,才会改善效率。
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)
改情况下,索引生效,执行效率高。
例如:
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表示我们
是按照常量查询。
如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
我们创建了符合索引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)
这里我们先注意一下, 由上面的分析,走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 没有用到索引。
注意:以下例子中,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)
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类型,它就会对这一部分的值进行隐式类型转换,之后这个索引字段就失效了。
尽量使用覆盖索引(只访问索引的查询(索引列完全包含查询列)),减少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:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
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)
如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
查询以科技开头的字符串。此时百分号在尾部,索引没有失效。
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)
根据前面的,我们在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条是北京市,那还不如全表扫描来的更快。
如果数据量比较大,而某一条数据占用的比例特别大,基本上覆盖了所有的比例,这时候就不会再走索引了,而走全表扫描。
此时我们已经为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底层会自动判断,使用索引有没有必要。
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)
尽量使用复合索引,而少使用单列索引 。
创建复合索引
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表中。
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:在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明你的表索引不正确或写入的查询没有利用索引。