文章目录
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子句中更多字段的索引。
在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量在创建索引时把这个字段放在索引次序的最后面。
本文详细探讨了MySQL中索引失效的各种情况,包括最佳左前缀原则、计算和函数导致的失效、类型转换的影响、范围条件的应用以及LIKE操作符和OR条件下的失效。通过具体的查询分析,揭示了如何避免这些情况以提高查询效率。
2064

被折叠的 条评论
为什么被折叠?



