MySQL——索引失效的案例

本文详细探讨了MySQL中索引失效的各种情况,包括最佳左前缀原则、计算和函数导致的失效、类型转换的影响、范围条件的应用以及LIKE操作符和OR条件下的失效。通过具体的查询分析,揭示了如何避免这些情况以提高查询效率。

MySQL——索引失效的案例

1、数据准备

建表

create table class(
    id int(11) not null auto_increment,
    className varchar(30) default null,
    address varchar(40) default null,
    monitor int null,
    primary key (id)
)engine = innodb auto_increment=1 default charset = utf8;

create table student(
    id int(11) not null auto_increment,
    stuno int not null ,
    name varchar(20) default null,
    age int(3) default null,
    classID int(11) default null,
    primary key (id)
)engine=innodb auto_increment=1 default charset = utf8;

创建函数

-- 开启创建函数:
set global log_bin_trust_function_creators = 1;
-- 
delimiter //
create function rand_string(n int)
    returns varchar(255)
begin
    declare chars_str varchar(100) default
        'abcdefghijklmnopqrstuvwsyzABCDEFGJHIKLMNOPQRSTUVWSYZ';
    declare return_str varchar(255) default '';
    declare i int default 0;
    while i < n do
        set return_str = concat(return_str,substring(chars_str,floor(1+rand()*52),1));
        set i = i + 1;
        end while ;
    return return_str;
end //
delimiter ;

-- 随机产生班级编号
delimiter //
create function rand_num(from_num int ,to_num int) returns int(11)
begin 
    declare  i int default 0;
    set i = floor(from_num +rand()*(to_num - from_num+1));
    return i;
end //
delimiter ;

创建存储过程

delimiter //
create procedure insert_stu (start int ,max_num int)
begin
    declare i int default 0;
    set autocommit = 0;
    repeat
        set i = i + 1;
        insert into student (stuno, name, age, classID) values ((start+i),rand_string(6),rand_num(1,50),rand_num(1,1000));
    until i = max_num
    end repeat ;
    commit ;
end //
delimiter ;

delimiter //
create procedure insert_class (max_num int)
begin
    declare i int default 0;
    set autocommit = 0;
    repeat
        set i = i + 1;
        insert into class (className, address, monitor) values (rand_string(8),rand_string(10),rand_num(1,100000));
    until i = max_num
    end repeat ;
    commit ;
end //
delimiter ;

调用存储过程

call insert_stu(100000,500000);
call insert_class(10000);

2、索引生效

2.1、全值匹配

建立索引:

create index idx_age_classid_name on student(age,classID,name);

查询的sql:

mysql> explain select * from student where age = 30 and classID = 4 and name = 'abcde';
+----+-------------+---------+------------+------+----------------------------------------------+----------------------+---------+-------------------+------+----------+-------+
| id | select_type | table   | partitions | type | possible_keys                                | key                  | key_len | ref               | rows | filtered | Extra |
+----+-------------+---------+------------+------+----------------------------------------------+----------------------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | student | NULL       | ref  | idx_age,idx_age_classid,idx_age_classid_name | idx_age_classid_name | 73      | const,const,const |    1 |   100.00 | NULL  |
+----+-------------+---------+------------+------+----------------------------------------------+----------------------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

根据查询的全部条件创建联合索引,查询效率会比创建部分索引效率高。

3、索引失效案例

3.1、最佳左前缀

在MySQL建立联合索引时会遵守最佳左前缀匹配原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。

索引:

create index idx_age_classid_name on student(age,classID,name);

对于联合索引,过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。

mysql> explain select * from student where age = 10 and name = 'abcd';
+----+-------------+---------+------------+------+----------------------------------------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys                                | key     | key_len | ref   | rows | filtered | Extra       |
+----+-------------+---------+------------+------+----------------------------------------------+---------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ref  | idx_age,idx_age_classid,idx_age_classid_name | idx_age | 5       | const | 9938 |    10.00 | Using where |
+----+-------------+---------+------------+------+----------------------------------------------+---------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

如果查询条件中没有使用这些字段中第1个字段时,联合索引不会被使用。

mysql> explain select * from student where classid = 1 and name = 'abcd';
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 499086 |     1.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

3.2、计算、函数导致索引失效

索引列使用函数

创建索引:

create index idx_name on student(name);

LEFT(str,n)函数:从左侧字截取符串str,返回字符串左边的n个字符,索引没有生效

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

在索引列上进行计算

创建索引:

create index idx_sno on student(stuno);

sql查询分析:索引没有生效。这是因为我们需要把索引字段的取值都取出来,然后依次进行表达式的计算来进行条件判断,因此采用的就是全表扫描的方式,效率会低很多。

mysql> explain select id,stuno,name from student where stuno+1 = 900001;
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 499086 |   100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

3.3、类型转换(自动或手动)导致索引失效

创建索引:

create index idx_name on student(name);

sql查询分析:索引没有生效。因为name是字符类型,要在条件中将数据使用引号引用起来,否则不使用索引

mysql> explain select * from student where name = 345;
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | idx_name      | NULL | NULL    | NULL | 499086 |    10.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)

3.4、范围条件右边的列索引失效

创建索引:

create index idx_age_classID_name on student(age,classID,name);

sql查询分析:使用索引的key_len为10(age字段占5(int类型长度4,null长度1),classID字段也占5个),所以只使用了联合索引中的age和classID字段的索引,name字段上的索引失效了。

mysql> explain select * from student where age=10 and classID>30 and name='abc';
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+-------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys        | key                  | key_len | ref  | rows  | filtered | Extra                 |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+-------+----------+-----------------------+
|  1 | SIMPLE      | student | NULL       | range | idx_age_classID_name | idx_age_classID_name | 10      | NULL | 18082 |    10.00 | Using index condition |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+-------+----------+-----------------------+
1 row in set, 1 warning (0.01 sec)

换种写法:把classID字段放在最后面

结果:name字段上的索引还是失效了,因为创建联合索引时的顺序已经确定了,是 age,classID,name ,当classID字段使用范围条件时,位于classID后面的name 字段索引肯定失效。

mysql>  explain select * from student where age=10 and name='abc' and classID>30 ;
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+-------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys        | key                  | key_len | ref  | rows  | filtered | Extra                 |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+-------+----------+-----------------------+
|  1 | SIMPLE      | student | NULL       | range | idx_age_classID_name | idx_age_classID_name | 10      | NULL | 18082 |    10.00 | Using index condition |
+----+-------------+---------+------------+-------+----------------------+----------------------+---------+------+-------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

3.5、不等或空值导致索引失效

不等于 !=或<>

创建索引:

create index idx_name on student(name);

sql查询分析:索引失效

mysql> explain select * from student where name <> 'abcd';
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | idx_name      | NULL | NULL    | NULL | 499086 |    50.15 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> explain select * from student where name != 'abcd';
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | idx_name      | NULL | NULL    | NULL | 499086 |    50.15 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

is not null 会导致索引失效

创建索引:

create index idx_name on student(name);

sql查询分析:is null 不会导致索引失效

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

-- is not null 会导致索引失效,
mysql> explain select * from student where age is not null;
+----+-------------+---------+------------+------+----------------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys        | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+----------------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | idx_age_classID_name | NULL | NULL    | NULL | 499086 |    50.00 | Using where |
+----+-------------+---------+------------+------+----------------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

3.6、like通配符%在开头导致索引失效

在使用LIKE关键字进行查询的查询语句中,如果匹配字符串的第一个字符为“%”,索引就不会起作用。只有“%"不在第一个位置,索引才会起作用。

创建索引:

create index idx_name on student(name);

sql查询分析:

-- 使用到索引
mysql> explain select * from student where name like 'abc%';
+----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | student | NULL       | range | idx_name      | idx_name | 63      | NULL |   22 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

-- 没有使用到索引
mysql> explain select * from student where name like '%abc';
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 499086 |    11.11 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

3.7、OR前后存在非索引的列导致索引失效

在WHERE子句中,如果在OR前的条件列进行了索引,而在OR后的条件列没有进行索引,那么索引会失效。也就是说,OR前后的两个条件中的列都是索引时,查询中才使用索引。

因为OR的含义就是两个只要满足一个即可,因此只有一个条件列进行了索引是没有意义的,只要有条件列没有进行索引,就会进行全表扫描,因此索引的条件列也会失效。

先创建name索引:

create index idx_name on student(name);

sql查询分析:没有使用到索引

mysql> explain select * from student where name='abc'or age=10;
+----+-------------+---------+------------+------+------------------------------+------+---------+------+--------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys                | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+---------+------------+------+------------------------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ALL  | idx_age_classID_name,idx_age | NULL | NULL    | NULL | 499086 |    11.88 | Using where |
+----+-------------+---------+------------+------+------------------------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

再创建age索引:

create index idx_age on student(age);

再次sql查询分析:使用到索引

mysql> explain select * from student where age=10 or name='abc';;
+----+-------------+---------+------------+-------------+---------------------------------------+------------------+---------+------+------+----------+--------------------------------------------+
| id | select_type | table   | partitions | type        | possible_keys                         | key              | key_len | ref  | rows | filtered | Extra                                      |
+----+-------------+---------+------------+-------------+---------------------------------------+------------------+---------+------+------+----------+--------------------------------------------+
|  1 | SIMPLE      | student | NULL       | index_merge | idx_age_classID_name,idx_name,idx_age | idx_age,idx_name | 5,63    | NULL | 9939 |   100.00 | Using union(idx_age,idx_name); Using where |
+----+-------------+---------+------------+-------------+---------------------------------------+------------------+---------+------+------+----------+--------------------------------------------+
1 row in set, 1 warning (0.00 sec)

3.8、总结

对于单列索引,尽量选择针对当前query过滤性更好的索引

在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。

在选择组合索引的时候,尽量选择能够包含当前query中的where子句中更多字段的索引。

在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量在创建索引时把这个字段放在索引次序的最后面。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

万里顾—程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值