5 索引优化原则&失效情况
创建表 插入数据
CREATE TABLE users( id INT PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(20) NOT NULL COMMENT '姓名', user_age INT NOT NULL DEFAULT 0 COMMENT '年龄', user_level VARCHAR(20) NOT NULL COMMENT '用户等级', reg_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间' ); INSERT INTO users(user_name,user_age,user_level,reg_time) VALUES('tom',17,'A',NOW()),('jack',18,'B',NOW()),('lucy',18,'C',NOW());
创建联合索引
ALTER TABLE users ADD INDEX idx_nal (user_name,user_age,user_level) USING BTREE;
# 1. 全值匹配
按索引字段顺序匹配使用。
EXPLAIN SELECT * FROM users WHERE user_name = 'tom';
EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age = 17
EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age = 17
AND user_level = 'A';
按顺序使用联合索引时, type类型都是 ref ,使用到了索引 效率比较高
# 2. 最佳左前缀法则
如果创建的是联合索引,就要遵循 最佳左前缀法则: 使用索引时,where后面的条件需要从索引的最左前列开始并且不跳过索引中的列使用。
场景1: 按照索引字段顺序使用,三个字段都使用了索引,没有问题。
EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age = 17 AND user_level = 'A';
场景2: 直接跳过user_name使用索引字段,索引无效,未使用到索引。
EXPLAIN SELECT * FROM users WHERE user_age = 17 AND user_level = 'A';
场景3: 不按照创建联合索引的顺序,使用索引
EXPLAIN SELECT * FROM users WHERE user_age = 17 AND user_name = 'tom' AND user_level = 'A';
where后面查询条件顺序是 user_age、user_level、user_name与我们建的索引顺序user_name、user_age、user_level不一致,为什么还是使用了索引,这是因为MySql底层优化器给咱们做了优化。
但是,最好还是要按照顺序 使用索引。
最佳左前缀底层原理
MySQL创建联合索引的规则是: 首先会对联合索引最左边的字段进行排序 ( 例子中是 user_name
), 在第一个字段的基础之上 再对第二个字段进行排序 ( 例子中是 user_age
)
所以: 最佳左前缀原则其实是个B+树的结构有关系, 最左字段肯定是有序的, 第二个字段则是无序的(联合索引的排序方式是: 先按照第一个字段进行排序,如果第一个字段相等再根据第二个字段排序). 所以如果直接使用第二个字段 user_age
通常是使用不到索引的.
# 3. 不要在索引列上做任何计算
不要在索引列上做任何操作,比如计算、使用函数、自动或手动进行类型转换,会导致索引失效,从而使查询转向全表扫描。
插入数据
INSERT INTO users(user_name,user_age,user_level,reg_time) VALUES('11223344',22,'D',NOW());
场景1: 使用系统函数 left()函数
EXPLAIN SELECT * FROM users WHERE LEFT(user_name, 6) = '112233';
where条件使用计算后的索引字段 user_name,没有使用索引,索引失效。
场景2: 字符串不加单引号 (隐式类型转换)
EXPLAIN SELECT * FROM users WHERE user_name = 11223344;
注:
Extra = Using where
表示Mysql将对storage engine提取的结果进行过滤,过滤条件字段无索引;( 需要回表去查询所需的数据 )
# 4. 范围之后全失效
存储引擎不能使用索引中范围条件右边的列
场景1: 条件单独使用user_name时,
type=ref
,key_len=82
-- 条件只有一个 user_name EXPLAIN SELECT * FROM users WHERE user_name = 'tom';
场景2: 条件增加一个 user_age ( 使用常量等值) ,
type= ref
,key_len = 86
EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age = 17;
场景3: 使用全值匹配,
type = ref
,key_len = 168
, 索引都利用上了.EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age = 17 AND user_level = 'A';
场景4: 使用范围条件时, avg > 17 ,
type = range
,key_len = 86
, 与场景3 比较,可以发现user_level
索引没有用上.EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age > 17 AND user_level = 'A';
# 5. 尽量使用覆盖索引
尽量使用覆盖索引(查询列和索引列尽量一致,通俗说就是对A、B列创建了索引,然后查询中也使用A、B列),减少select *的使用。
场景1: 全值匹配查询, 使用
select *
EXPLAIN SELECT * FROM users WHERE user_name = 'tom' AND user_age = 17 AND user_level = 'A';
场景1: 全值匹配查询, 使用
select 字段名1 ,字段名2
EXPLAIN SELECT user_name , user_age , user_level FROM users WHERE user_name = 'tom' AND user_age = 17 AND user_level = 'A';
使用覆盖索引(查询列与条件列对应),可看到Extra从Null变成了Using index,提高检索效率。
注:
Using index
表示 使用到了索引 , 并且所取的数据完全在索引中就能拿到,(使用覆盖索引的时候就会出现)
# 6. 使用不等于(!=或<>)会使索引失效
使用 != 会使type=ALL,key=Null,导致全表扫描,并且索引失效。
使用
!=
EXPLAIN SELECT * FROM users WHERE user_name != 'tom';
# 7. is null 或 is not null也无法使用索引
在使用is null的时候,索引完全失效,使用is not null的时候,type=ALL全表扫描,key=Null索引失效。
场景1: 使用
is null
EXPLAIN SELECT * FROM users WHERE user_name IS NULL;
场景2: 使用
not null
EXPLAIN SELECT * FROM users WHERE user_name IS NOT NULL;
# 8. like通配符以%开头会使索引失效
like查询为范围查询,%出现在左边,则索引失效。%出现在右边索引未失效。口诀:like百分加右边。
场景1
EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom%';
场景2
EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom';
场景3
EXPLAIN SELECT * FROM users WHERE user_name LIKE 'tom%';
注:
Using index condition
表示 查找使用了索引,但是需要;';查询数据
解决%出现在左边索引失效的方法:使用覆盖索引。
Case1:
EXPLAIN SELECT user_name FROM users WHERE user_name LIKE '%jack%';
- 对比场景1可以知道, 通过使用覆盖索引 type = index,并且使用了 Using index,从全表扫描变成了全索引扫描.
注:
Useing where; Using index;
查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据
Case2:
EXPLAIN SELECT id FROM users WHERE user_name LIKE '%jack%';
- 这里出现
type=index
,因为主键自动创建唯一索引。
Case3:
EXPLAIN SELECT user_name,user_age FROM users WHERE user_name LIKE '%jack%';
EXPLAIN SELECT user_name,user_age,user_level FROM users WHERE user_name LIKE '%jack%';
EXPLAIN SELECT id,user_name,user_age,user_level FROM users WHERE user_name LIKE '%jack%';
- 上面三组, explain执行的结果都相同,表明都使用了索引.
Case4:
EXPLAIN SELECT id,user_name,user_age,user_level,reg_time FROM users WHERE user_name
LIKE '%jack%';
分析:由于只在(user_name,user_age,user_level)上创建索引, 当包含reg_time时,导致结果集偏大(reg_time未建索引)【锅大,锅盖小,不能匹配】,所以type=ALL。
like 失效的原理
- %号在右: 由于B+树的索引顺序,是按照首字母的大小进行排序,%号在右的匹配又是匹配首字母。所以可以在B+树上进行有序的查找,查找首字母符合要求的数据。所以有些时候可以用到索引.
- %号在左: 是匹配字符串尾部的数据,我们上面说了排序规则,尾部的字母是没有顺序的,所以不能按照索引顺序查询,就用不到索引.
- 两个%%号: 这个是查询任意位置的字母满足条件即可,只有首字母是进行索引排序的,其他位置的字母都是相对无序的,所以查找任意位置的字母是用不上索引的.
# 9. 字符串不加单引号导致索引失效
varchar类型的字段,在查询的时候不加单引号导致索引失效,转向全表扫描。
场景1
SELECT * FROM users WHERE user_name = '123'; SELECT * FROM users WHERE user_name = 123;
上述两条sql语句都能查询出相同的数据。
场景2:
通过explain执行结果可以看出,字符串(name)不加单引号在查询的时候,导致索引失效(type=ref变成了type=ALL,并且key=Null),并全表扫描。
# 10. 少用or,用or连接会使索引失效
在使用or连接的时候 type=ALL
,key=Null,索引失效,并全表扫描。