Mysql索引失效问题demo

# 1. 准备工作
CREATE TABLE `user` (
  `id` INT NOT NULL AUTO_INCREMENT,
  `code` VARCHAR(20) COLLATE utf8mb4_bin DEFAULT NULL,
  `age` INT DEFAULT '0',
  `name` VARCHAR(30) COLLATE utf8mb4_bin DEFAULT NULL,
  `height` INT DEFAULT '0',
  `address` VARCHAR(30) COLLATE utf8mb4_bin DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_code_age_name` (`code`,`age`,`name`),
  KEY `idx_height` (`height`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

INSERT INTO test.user (id, CODE, age, NAME, height,address) VALUES (1, '101', 21, '周星驰', 175,'香港');
INSERT INTO test.user (id, CODE, age, NAME, height,address) VALUES (2, '102', 18, '周杰伦', 173,'台湾');
INSERT INTO test.user (id, CODE, age, NAME, height,address) VALUES (3, '103', 23, '苏东坡', 174,'北宋');

# 8.0.28
SELECT VERSION();


EXPLAIN SELECT * FROM USER WHERE id=1;
# 由于id字段是主键,该sql语句用到了主键索引。

# 2. 不满足最左匹配原则
EXPLAIN SELECT * FROM USER WHERE CODE='101';

EXPLAIN SELECT * FROM USER WHERE CODE='101' AND age=21 

EXPLAIN SELECT * FROM USER WHERE CODE='101' AND age=21 AND NAME='周星驰';
# 上面三种情况,sql都能正常走索引。

EXPLAIN SELECT * FROM USER WHERE CODE = '101'  AND NAME='周星驰';
# 查询条件原本的顺序是:code、age、name,但这里只有code和name中间断层了,掉了age字段,这种情况也能走code字段上的索引。
# 这4条sql中都有code字段,它是索引字段中的第一个字段,也就是最左边的字段。只要有这个字段在,该sql已经就能走索引。
# 最左匹配原则

## 以下未应用索引
EXPLAIN SELECT * FROM USER WHERE age=21;
EXPLAIN SELECT * FROM USER WHERE age=21 AND NAME='周星驰';
EXPLAIN SELECT * FROM USER WHERE NAME='周星驰';
# 说明以上3种情况不满足最左匹配原则,说白了是因为查询条件中,没有包含给定字段最左边的索引字段,即字段code。

# 3. 使用了select *
EXPLAIN SELECT * FROM USER WHERE NAME='苏东坡';
# 在该sql中用了select *,从执行结果看,走了全表扫描,没有用到任何索引,查询效率是非常低的。

EXPLAIN 
SELECT CODE,NAME FROM USER 
WHERE NAME='苏东坡';
# 如果select语句中的查询列,都是索引列,那么这些列被称为覆盖索引。这种情况下,查询的相关字段都能走索引,索引查询效率相对来说更高一些。
# 而使用select *查询所有列的数据,大概率会查询非索引列的数据,非索引列不会走索引,查询效率非常低。

# 4. 索引列上有计算
EXPLAIN SELECT * FROM USER WHERE id=1;
# 由于id字段是主键,该sql语句用到了主键索引。

EXPLAIN SELECT * FROM USER WHERE id+1=2;
# 从上图中的执行结果,能够非常清楚的看出,该id字段的主键索引,在有计算的情况下失效了。

# 5. 索引列用了函数
EXPLAIN SELECT * FROM USER  WHERE height=17;

EXPLAIN SELECT * FROM USER  WHERE SUBSTR(height,1,2)=17;
# 这时需要用到SUBSTR函数,用它截取了height字段的前面两位字符,从第一个字符开始。
# 在使用该函数之后,该sql语句竟然走了全表扫描,索引失效了。

# 6. 字段类型不同
EXPLAIN 
SELECT * FROM USER WHERE CODE="101";
# 温馨提醒一下,查询字符字段时,用双引号“和单引号都可以。

# 但如果你在写sql时,不小心把引号弄掉了,把sql语句变成了:
EXPLAIN 
SELECT * FROM USER WHERE CODE=101;
# 该sql语句竟然变成了全表扫描。因为少写了引号,这种小小的失误,竟然让code字段上的索引失效了。
# 因为code字段的类型是varchar,而传参的类型是int,两种类型不同。

# 如果int类型的height字段,在查询时加了引号条件,却还可以走索引:
EXPLAIN SELECT * FROM USER 
WHERE height='175';
# 该sql语句确实走了索引。int类型的参数,不管在查询时加没加引号,都能走索引。

# mysql发现如果是int类型字段作为查询条件时,它会自动将该字段的传参进行隐式转换,把字符串转换成int类型。

SELECT 1 + '1';
# mysql自动把字符串1,转换成了int类型的1,然后变成了:1+1=2。

SELECT CONCAT(1,'1');
# 如果你确实想拼接字符串该怎么办?
# 答:可以使用concat关键字。

/**
关键问题来了:为什么字符串类型的字段,传入了int类型的参数时索引会失效呢?

答:根据mysql官网上解释,字符串'1'、' 1 '、'1a'都能转换成int类型的1,也就是说可能会出现多个字符串,对应一个int类型参数的情况。那么,mysql怎么知道该把int类型的1转换成哪种字符串,用哪个索引快速查值?

感兴趣的小伙伴可以再看看官方文档:https://dev.mysql.com/doc/refman/8.0/en/type-conversion.html
**/

# 7. like左边包含%
SELECT * FROM USER WHERE NAME LIKE '李%';
/**
目前like查询主要有三种情况:

like '%a'
like 'a%'
like '%a%'
**/

EXPLAIN SELECT * FROM USER
WHERE CODE LIKE '10%';


EXPLAIN SELECT * FROM USER
WHERE CODE LIKE '%1';
# 这种%在1左边时,code字段上索引失效了,该sql变成了全表扫描。

EXPLAIN SELECT * FROM USER
WHERE CODE LIKE '%1%';
# 该sql语句的索引也会失效。

/**
那么,为什么会出现这种现象呢?
答:其实很好理解,索引就像字典中的目录。一般目录是按字母或者拼音从小到大,从左到右排序,是有顺序的。
我们在查目录时,通常会先从左边第一个字母进行匹对,如果相同,再匹对左边第二个字母,如果再相同匹对其他的字母,以此类推。
通过这种方式我们能快速锁定一个具体的目录,或者缩小目录的范围。
但如果你硬要跟目录的设计反着来,先从字典目录右边匹配第一个字母不现实。
**/

# 8. 列对比
EXPLAIN SELECT * FROM USER 
WHERE id=height
/**
id字段本身是有主键索引的,同时height字段也建了普通索引的,并且两个字段都是int类型,类型是一样的。
但如果把两个单独建了索引的列,用来做列对比时索引会失效。
**/

# 9. 使用or关键字
EXPLAIN SELECT * FROM USER 
WHERE id=1 OR height='175';


EXPLAIN SELECT * FROM USER 
WHERE id=1 OR height='175' OR address='北宋';
# 之前的索引都失效了。因为你最后加的address字段没有加索引,从而导致其他字段的索引都失效了。
# 注意:如果使用了or关键字,那么它前面和后面的字段都要加索引,不然所有的索引都会失效,这是一个大坑。

# 10. not in和not exists
/**
在我们日常工作中用得也比较多的,还有范围查询,常见的有:
in
exists
not in
not exists
between and
**/
EXPLAIN SELECT * FROM USER
WHERE height IN (173,174,175,176);
# sql语句中用in关键字是走了索引的。

EXPLAIN SELECT * FROM USER  t1
WHERE  EXISTS (SELECT 1 FROM USER t2 WHERE t2.height=173 AND t1.id=t2.id)
# 用exists关键字同样走了索引。

/**
上面演示的两个例子是正向的范围,即在某些范围之内。
那么反向的范围,即不在某些范围之内,能走索引不?
**/
EXPLAIN SELECT * FROM USER
WHERE height NOT IN (173,174,175,176);
# 索引失效了。

EXPLAIN SELECT * FROM USER
WHERE id  NOT IN (173,174,175,176);
# 主键字段中使用not in关键字查询数据范围,任然可以走索引。而普通索引字段使用了not in关键字查询数据范围,索引会失效。

EXPLAIN SELECT * FROM USER  t1
WHERE  NOT EXISTS (SELECT 1 FROM USER t2 WHERE t2.height=173 AND t1.id=t2.id)
# sql语句中使用not exists关键后,t1表走了全表扫描,并没有走索引。

# 11. order by的坑
# order by后面的条件,也要遵循联合索引的最左匹配原则。
EXPLAIN SELECT * FROM USER
ORDER BY `code` LIMIT 100;

EXPLAIN SELECT * FROM USER
ORDER BY `code`,age LIMIT 100;

EXPLAIN SELECT * FROM USER
ORDER BY `code`,age,NAME LIMIT 100;
# 索引失效了?? 
# 除了遵循最左匹配原则之外,有个非常关键的地方是,后面还是加了limit关键字,如果不加它索引会失效。

## 查找的是索引列,会适应索引。
EXPLAIN SELECT CODE,NAME FROM USER
ORDER BY `code`,age,NAME LIMIT 100;

EXPLAIN SELECT * FROM USER
WHERE CODE='101'
ORDER BY age;
# order by还能配合where一起遵循最左匹配原则。

EXPLAIN SELECT * FROM USER
WHERE CODE='101'
ORDER BY NAME;
# 虽说name是联合索引的第三个字段,但根据最左匹配原则,该sql语句依然能走索引,因为最左边的第一个字段code,在where中使用了。只不过order by的时候,排序效率比较低,需要走一次filesort排序

EXPLAIN SELECT * FROM USER
ORDER BY CODE DESC,age DESC LIMIT 100;
# order by后面的code和age字段都用了降序,所以依然走了索引  失效了??

## 查找的是索引列,会适应索引。
EXPLAIN SELECT CODE,NAME FROM USER
ORDER BY CODE DESC,age DESC LIMIT 100;

EXPLAIN SELECT * FROM USER
WHERE CODE='101'
ORDER BY CODE, NAME;
# code字段在where和order by中都有,对于这种情况,从图中的结果看出,还是能走了索引的。

EXPLAIN SELECT * FROM USER
ORDER BY CODE, NAME;
# 如果order by语句中没有加where或limit关键字,该sql语句将不会走索引。


EXPLAIN SELECT * FROM USER
ORDER BY CODE, height LIMIT 100;
# 如果对多个索引进行order by,索引也失效了。

EXPLAIN SELECT * FROM USER
ORDER BY NAME LIMIT 100;
# name字段是联合索引的第三个字段,从图中看出如果order by不满足最左匹配原则,确实不会走索引。

EXPLAIN SELECT * FROM USER
ORDER BY CODE ASC,age DESC LIMIT 100;
# 尽管order by后面的code和age字段遵循了最左匹配原则,但由于一个字段是用的升序,另一个字段用的降序,最终会导致索引失效。