分类

按数据结构分类:b+ tree索引、Hash索引、Full-text索引
按物理存储分类:聚簇索引(主键索引)、二级索引(辅助索引)
按字段特性分类:主键索引、唯一索引、普通索引、前缀索引
按字段个数分类:单列索引、联合索引

按数据结构分类

索引类型 InnDB引擎 MyISAM引擎 Memory引擎
B+Tree 索引 Yes Yes Yes
HASH索引 NO NO Yes
Full-Text Yes Yes NO

InnoDB是Mysql默认存储引擎,B+Tree也是使用最多的引擎
索引列选择:

  • 如果有主键,默认会使用主键作为聚簇索引的索引键(key);
  • 如果没有主键,就选择第一个不包含 NULL 值的唯一列作为聚簇索引的索引键(key);
  • 在上面两个都没有的情况下,InnoDB 将自动生成一个隐式自增 id 列作为聚簇索引的索引键(key);

B+ Tree

B+Tree 是一种多叉树,叶子节点存放数据,非叶子节点存放索引,叶子节点间用双向链表链接。

查询过程

select*from product where id=5;
  1. 将5与跟节点点索引数据(1,10,20)比较,找到在1-10之间,继续向第二层索引数据找
  2. 在第二层索引数据(1,4,7)中进行查找,找到在4-7之间,继续向第三层索引查找
  3. 直到找到索引值为5的行数据

B+Tree对于千万级数据也只需要3-4层高度就能满足,相比于B树和二叉树来说,最大的优势在于查询效率很高,因为在数据量很大的情况下,查询一个数据的磁盘I/O依然维持在3-4次。

二级索引查询数据的过程

主键搜音的B+Tree和二级索引的B+Tree的区别:

  • 主键索引的B+Tree的叶子节点存放的实际数据,所有完整的用户记录都存放在主键索引的B+Tree的叶子节点里
  • 二级索引的B+Tree的叶子节点存放的是主键值,而不是实际数据。

例如执行查询语句:


select * from product where product_no = '0002';

先在二级索引查询,获得主键值,再去主键索引查询行数据。这个过程叫回表,需要查询两个B+Tree才能查到数据。

但是在二级索引就能获得结果的过程叫「覆盖索引」,只需要查询一个B+Tree
例如:


select id from product where product_no = '0002';

为什么选择b+Tree

  • B+Tree与B Tree

  • B+Tree与二叉树
    二叉树儿子节点只有2个,搜索复杂度比B+Tree高很多

  • B+Tree与hash
    Hash只适合做等值查询,不适合做范围查询

按物理存储分类

聚簇索引、二级索引,上面已经讨论区别。

按字段特性分类

主键索引

建立在主键字段上的索引,通常在创建表的时候一起创建,一张表最多只有一个主键索引,索引列不允许空值

唯一索引

建立在UNIQUE字段上的索引,一张表可以有多个唯一索引,索引列的值必须唯一,但是允许有空值。

普通索引

建立在普通字段上的索引,即不要求字段为主键,也不要求是UNIQUE。

前缀索引

对字符类型字段前几个字符建立索引,而不是整个字段上建立索引。

按字段个数分类

单列索引、联合索引

  • 建立在单列上的索引称为单列索引
  • 建立在多列上的索引称为联合索引

联合索引

联合索引将多个字段作为索引,先按照第一个key比较,在第一个key相同的情况下,再根据第二个字段比较,以此类推。
因此,联合索引 存在最左匹配原则,如果在使用时不遵循最左匹配原则就会导致索引失效。

联合索引范围查询
并不是查询过程使用了联合索引查询,就代表联合索引中的所有字段都用到了联合索引进行索引查询,可能部分字段没有用到联合索引。
这种情况发生在范围查询。范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引。
联合索引为(a,b)
例一:


select * from tb where a>1 and b=2;

a>1会走索引,但是在a>1条件的二级索引范围中,b的字段是无序的,所以b=2是不会走索引的。

例二:


select * from tb where a>=1 and b=2;

a>=1会走索引,虽然在a>1的范围中,b是无序的,但是在a=1的范围中,b是有序的。所以在查找a>=1 and b=2时,会从a=1 and b=2这条记录开始查询,会减少一些查询范围,a和b都走了索引。

例三:


select * from tb where a between 2 and 8 and b=2;

a和b都走了联合索引

例四:


select * from tb where name like 'j%' and age=22;

与例二相同,字符串会以字段的值排序,所以都会走联合索引。

总结
综上所示,联合索引的最左匹配原则,在遇到范围查询(如 >、<)的时候,就会停止匹配,也就是范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引。注意,对于 >=、<=、BETWEEN、like 前缀匹配的范围查询,并不会停止匹配,前面我也用了四个例子说明了

索引下推

  • 在 MySQL 5.6 之前,只能从 ID2 (主键值)开始一个个回表,到「主键索引」上找出数据行,再对比 b 字段值。

  • 而 MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在联合索引遍历过程中,对联合索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数

当你的查询语句的执行计划里,出现了 Extra 为 Using index condition,那么说明使用了索引下推的优化。

索引区分度

建立联合索引时,要把区分度大的字段排在前面,这样区分度大的字段越有可能被更多的sql使用到。

区分度时某个字段不同值的个数除以表的总行数。

联合索引进行排序


select * from order where status=1 order by create by create_time asc

对于这个查询语句可以对status和cerate_time 建立一个联合索引,在status筛选完之后就已经排好了create_time 排好了序,提高了查询效率。

什么时候需要创建索引

索引缺点:

  • 需要占用物理空间,数量越大,占用空间越大
  • 创建索引和维护索引要耗时间,数据量越大,耗费时间越多
  • 降低增删改的效率,每次增删改都会进行动态维护。

需要索引

  • 字段有唯一性限制的,比如商品编码;
  • 经常用于 WHERE 查询条件的字段,这样能够提高整个表的查询速度,如果查询条件不是一个字段,可以建立联合索引。
  • 经常用于 GROUP BYORDER BY 的字段,这样在查询的时候就不需要再去做一次排序了,因为我们都已经知道了建立索引之后在 B+Tree 中的记录都是排序好的。

不需要索引

  • WHERE 条件,GROUP BYORDER BY 里用不到的字段,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的,因为索引是会占用物理空间的。
  • 字段中存在大量重复数据,不需要创建索引,比如性别字段,只有男女,如果数据库表中,男女的记录分布均匀,那么无论搜索哪个值都可能得到一半的数据。在这些情况下,还不如不要索引,因为 MySQL 还有一个查询优化器,查询优化器发现某个值出现在表的数据行中的百分比很高的时候,它一般会忽略索引,进行全表扫描。
  • 表数据太少的时候,不需要创建索引;
  • 经常更新的字段不用创建索引,比如不要对电商项目的用户余额建立索引,因为索引字段频繁修改,由于要维护 B+Tree的有序性,那么就需要频繁的重建索引,这个过程是会影响数据库性能的。

优化索引

  • 前缀索引优化;
  • 覆盖索引优化;
  • 主键索引最好是自增的;
  • 防止索引失效;

前缀索引优化

以某个字段的前几个字符建立索引,减少索引字段的大小
缺点:

  • order by 就无法使用前缀索引;
  • 无法把前缀索引用作覆盖索引;

覆盖索引优化

将需要的字段建立成联合索引,可以直接从二级索引中查询到记录,避免回表操作,减少I/O操作。

主键索引

使用自增主键,可以让新记录按顺序添加,不用移动之前有的数据。而使用非自增主键,会导致页分裂,可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率。

索引最好设置为NOT NULL

  • 第一原因:索引列存在 NULL 就会导致优化器在做索引选择的时候更加复杂,更加难以优化,因为可为 NULL 的列会使索引、索引统计和值比较都更复杂,比如进行索引统计时,count 会省略值为NULL 的行。

  • 第二个原因:NULL 值是一个没意义的值,但是它会占用物理空间,所以会带来的存储空间的问题,因为 InnoDB 存储记录的时候,如果表中存在允许为 NULL 的字段,那么行格式中 1 字节空间存储 NULL 值列表

防止索引失效

索引失效:

  • 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;
  • 当我们在查询条件中对索引列做了计算、函数、类型转换操作,这些情况下都会造成索引失效;
  • 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
  • 在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。