vlambda博客
学习文章列表

Mysql什么时候建索引、什么时候不适合建索引

今天我们介绍的是MySQL设置索引的基本原则。划重点,这是面试官经常问到的问题。



主键索引

大家在设计主键的时候一定要是自增的,不建议使用UUID作为主键因为UUID是无序的,MySQL在维护聚簇索引的时候都是按照主键的顺序排序的,也就是说每个数据页中的数据是按照主键从小到大排序的,而且,数据与数据之前是通过单向链表连接的,上一个数据页中的最大的主键的值一定是小于下一个数据页中的最小的主键的值,数据页和数据页之间是通过双向链表来维护的。画个图帮助大家理解

如果主键是自增的,MySQL只需要根据主键目录能很快的定位到新增的记录应该插入到哪里,如果主键不是自增的那么每次都需要从头开始比较,然后找到合适的位置,再将记录插入进去,这样严重影响效率,所以主键的设计一定要是自增的。

另外唯一索引和主键索引类似,但是唯一索引不一定是自增的,所以维护唯一索引的成本肯定是大于主键索引的。但是唯一索引的值是唯一的,可以更快的通过索引字段来确定一条记录,但是可能需要进行回表查询。


为频繁查询的字段建立索引

为那些经常作为查询条件的字段建立索引,这样能够提高整个表的查询速度。但是查询条件一般不是一个字段,所以一般是建立的联合索引。另外查询条件中一般会有like这样的模糊查询,如果是模糊查询请最好遵守最左前缀查询原则


避免为"大字段"建立索引

举个例子来说,假设有两个,一个是varchar(5),一个是varchar(200),这种情况下优先选择为varchar(5)的字段建立索引,因为MySQL在维护索引的时候会将字段值一起维护的,那这样会导致索引占用更多的空间,在排序时也需要花费更多的时间去对比。

那假如就要为varchar(200)建立索引呢?那就取部分数据,例如 address 类型为varchar(200),在建立索引的时候可以这么写:

 
   
   
 
CREATE INDEX  tbl_address ON dual(address(20));

选择区分度大的列作为索引

简单来说就是某个数据列包含许多重复的内容,不要建立索引

假设现在有一个"性别"字段,里面存放的数据的值要么是男,要么是女,那么这样的字段很不适合作为索引。因为如果值出现的几率几乎相等,那么无论搜索哪个值都可能得到一半的数据。在这些情况下,mysql查询优化器发现重复值百分比很高的时候,它一般会忽略索引,进行全表扫描。

惯用的百分比界线是"30%"。(匹配的数据量超过一定限制的时候查询器会放弃使用索引


尽量为ORDER BY 和 GROUP BY 后面的字段建立索引

将 Order By后面的字段建立索引,这样在查询的时候就不需要再去做一次排序了,因为我们都已经知道了建立索引之后在B+树中的记录都是排序好的。

GROUP BY 和 ORDER BY 其实是类似,所以将这两个放在一起说了。

因为在GROUP BY 的时候也要先根据 GROUP BY 后面的字段排序,然后在执行聚合操作。

如果 GROUP BY 后面的字段没有排序,那么这个时候MySQL是需要先进行排序的,这样就会产生临时表,一个排好序的临时表,然后再在临时表中执行聚合操作,这样子当然效率很低了,如果 GROUP BY 后面的字段已经建立了索引,那么MySQL 就不需要再去排序,也就不会产生临时表。

然而比较坑的是,如果 GROUP BY的列和 ORDER BY的列不一样,即使都有索引也会产生临时表,其实对于这些情况我网上搜了下好像还很多,这里我给大家列出来,说实话,这些虽然是标准,但是这个标准好像很难实现,因为实际的场景肯定没这么简单和单纯

 
   
   
 
1. 如果GROUP BY 的列没有索引,产生临时表.

2. 如果GROUP BY时,SELECT的列不止GROUP BY列一个,并且GROUP BY的列不是主键 ,产生临时表. 

3. 如果GROUP BY的列有索引,ORDER BY的列没索引.产生临时表. 

4. 如果GROUP BY的列和ORDER BY的列不一样,即使都有索引也会产生临时表. 

5. 如果GROUP BY或ORDER BY的列不是来自JOIN语句第一个表.会产生临时表. 

6. 如果DISTINCT 和 ORDER BY的列没有索引,产生临时表.

7. GROUP BY 和 ORDER BY 的列一样且是主键,但SELECT 列含有除GROUP BY列之外的列,也会产生临时表 




<<< 左右滑动见更多 >>>


不要在条件中使用函数

因为MySQL为该索引维护的B+树就是基于该字段原始数据的,如果在使用过程中加了函数,MySQL就会认为这个是原来的字段,那肯定不会走索引了。

如果非要使用到函数怎么办?那么在建立索引的时候可以连着函数一起创建。假设有一个字段叫age,并为其创建了索引,但是使用的时候是这样子的

 
   
   
 
SELECT * FROM student WHERE round(age) = 2;

这个时候索引是使用不到的,那么如果真的非要让round(age)也走索引,那么你可以这么创建索引

 
   
   
 
create index stu_age_round on test(round(age)); 

这个时候在通过上面的方式去查询,索引就是生效的。



不要建立太多的索引

因为MySQL维护索引是需要空间和耗费性能的,MySQL会为每个索引字段维护一颗B+树。所以索引过多,增加了MySQL的负担。



频繁增删改的字段不要建立索引

因为字段频繁修改,那就意味着需要频繁的重建索引,这必然影响MySQL的性能啊。

说到这里大部分说的是索引设计的时候需要注意的一些原则,其实真正的原则还是需要根据实际的业务变更的,没有所谓的“公式”,只要适合自己实际的业务场景的设计才是最好的。所以大家也不要过于追求“优化”,因为这样往往会适得其反,毕竟脱离了业务谈技术就是在耍流氓。


索引失效的常见场景
  1. 查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等)

  2. 对小表查询

  3. 使用  OR  关键字
  4. 联合索引不遵循最左前缀原则
  5. 使用模糊查询的时候以%开头也会导致索引失效
  6. 对索引列进行运算,需要建立函数索引.
  7. not in ,not exist
  8. 随着表的增长,where条件出来的数据太多,大于15%,使得索引失效(会导致CBO计算走索引花费大于走全表)
  9. 如果MySQL估计使用索引比全表扫描更慢,则不使用索引
  10. 索引列如果使用了隐式转换也会导致索引失效,字符型字段为数字时在where条件里不添加引号
假设字段  age  类型为 int,那我们一般是这么查询的

SELECT * FROM student WHERE age=15

上面这种情况是能使用到索引的,但是如果你这么写

SELECT * FROM student WHERE age='15'

那这种情况是使用不到索引的,也就是 age 列情的索引是失效的。
其他的一些原则请大家还是要去看下索引的原理和查询的基本原则,如果没有前面的铺垫,这些看起来似乎有些空洞。所以请大家在索引这一块一定要循序渐进的学习,这一块基本也是我们平时在使用 MySQL 时候的一些核心知识点了。

总结