MySQL索引

索引的优势与劣势

简而言之,优势——能大大加快数据的查询速度。劣势——增加了额外的空间来存储索引,且执行插入、删除操作语句时增加了维护索引的成本。

所以建立了索引就一定是要用起来的,不然不如不建立,而且索引对于优化查询的效果是否大,和我们如何写索引(使用哪种索引?存储引擎?),如何写SQL语句,以及数据(数据量、数据类型、数据分布等)有很大关系。

不过总体来说使用索引的优势还是大于不使用。

索引的类型分类

  1. 按数据结构分:

    可以分为B+Tree索引、哈希索引、Full-Text索引,一般使用的都是B+Tree索引。

  2. 按物理存储分:

    分为聚簇索引(也称主键索引)非聚簇索引(或称二级索引、辅助索引),说聚簇索引,那就是在说InnoDB存储引擎了,InnoDB存储引擎在存储数据时,会使用主键索引的方式,将索引和数据放在一起,数据是按照主键进行有序存储的。二级索引是另外一个空间中,存储了id(主键)和索引字段。

    就想象有两棵B+树,一棵的叶子节点上存储了所有字段数据,且按照id排序;另一棵的叶子节点上存储的是id和索引字段,且按照索引字段排序。

  3. 按字段特性分:

    分为主键索引、唯一索引、前缀索引、普通索引。这个见名知义了。主键索引就是以主键为索引(非空且唯一);唯一索引就是索引字段唯一;前缀索引指的是利用varchar、char等类型的前几个字符建立的索引;普通索引就是一般的索引(字段可以为null、也可以不唯一)

  4. 按字段个数分:单列索引、联合索引。比如对(score)字段建立索引,也可以是对(score,name)两个字段建立联合索引。

如何用好索引

众锁周知,索引是把双刃剑,既带来了查询性能的提升,又带来了维护索引的开销。因此我们要结合具体的场景来分析是否要用索引,也要知道什么样的场景索引会生效、什么场景索引会失效。

适合使用索引的场景

  1. whereorder by中频繁使用到的字段
  2. 索引字段区分度较高
  3. 在进行多表查询时,对外键增加索引可以大大提高join操作效率
  4. 建立索引的字段不应过长、如果太长可以考虑使用前缀索引
  5. 建立联合索引时,应遵循最左匹配原则,把使用频率最高的放在最左边
  6. 尽量使用联合索引代替单列索引

不适合使用索引的场景

  1. 字段不在whereorder by中使用
  2. 字段区分度低(比如性别)
  3. 经常删改的字段不适合建立索引
  4. 索引不能参与计算,会导致索引失效,所以经常要参与计算的字段不应建立索引
  5. 索引字段无序时不建议建立索引,会导致页分裂

索引失效场景

  1. 查询带有or,且其中一个条件的字段没用到索引

  2. 使用了联合索引,但是sql查询时没有最左匹配

  3. 模糊查询(like)字符以%开头

  4. 索引字段参与计算

  5. 索引字段使用函数计算

  6. 隐式类型转换导致索引失效,比如数据库中phone是varchar类型,然后where条件写了个整数类型。相当于对字段使用了CAST类型装换函数,所以索引失效(MySQL在比较字符和数字时,是将字符转换为数字)

  7. 不同字段值对比导致索引失效(?)

  8. 反向操作,比如写NOT IN、IS NOT NULL、!=之类的。

其他知识点

可以使得知识体系更完善。(当然硬要说还可以说存储引擎——InnoDB、MyISAM)

索引覆盖

索引覆盖,就是要查询的列,在索引中已经包含,则被所使用的的索引覆盖,不需要再回表。

假设有一个联合索引为idx(a,b,c),where过滤条件符合最左匹配原则,且select查询时只查询了ab两个字段,那么直接在二级索引表中查出数据返回,不用去得到id值、然后再进行回表,在主键索引中查找对应数据。

所以如果只要查询a、b、c三个字段信息的话,就不要写select *,避免不必要的回表操作。

最左匹配原则

指的是,使用索引时,存在最左匹配的规则。

假设有一个联合索引idx(a,b,c)

  1. where过滤条件必须先有a、再有b、再有c。由于order by优先级比where低——所以假设where条件中是b,order by中是a,那么这个idx不生效。
  2. 当a是严格大于(>)或严格小于(<)时,那么后面的b、c都不生效。但是如果a是≥或≤,则b、c照样生效。

注:由于优化器的存在,where中字段的顺序不影响最左匹配(即where a = ? and b = ? 等价于 where b = ? and a = ?

索引下推

索引下推,就是将Server层筛选数据的工作,放到了执行引擎层来做。

这个机制是MySQL 5.6新增的优化机制,默认开启。

无索引下推时的查询:

无索引下推

有索引下推时的查询:

MySQL有索引下推时的查询

可以发现,有索引下推后,回表的次数就变少了。(原先需要对4条数据通过id进行回表查询、而现在只有两条数据需要回表查询)。

CBO:基于成本的优化器

CBO,即Cost-based-Optimizer,基于成本的优化器,会通过计算一条sql语句的使用索引的成本的来决定是否使用or使用哪条索引。

该部分参考自技术文章摘录——索引出错:请理解CBO的工作原理

两个“奇怪”的案例:

案例1:明明两次查询时的SQL语句均相同,为什么一次使用了索引,一次没有使用索引?

通常是因为优化器认为使用索引的cost(成本)高于全表扫描。比如一条范围查询语句,但是满足条件的记录数很多,近乎与全表数据了,此时即使可以走索引,但是还要再全部回表,执行成本就很高。(不如直接全表扫描)

案例2:索引建立在有限状态的字段上

假设有一张订单表,我们对于其状态建立了索引,而状态可能有未完成、进行中、已完成这几类。通常订单状态大部分是已完成的,而未完成和进行中相比是少量的,这就存在数据倾斜,而优化器却认为订单状态是平均分布的,导致对于未完成和进行中的订单,优化器认为记录数很多,基于索引方式+回表查询成本太高。解决方案是,让优化器能知道数据的分布,可以利用MySQL 8.0的直方图功能,创建一个直方图,使其知道数据分布,从而更好地选择执行计划。

实践

联合索引(a,b,c),下面的查询语句会不会走索引?如果走,具体是哪些字段能走?

  1. select * from t_test where a = 1 and b = 2 and c = 3;
  2. select * from t_test where a = 1 and b > 2 and c = 3;
  3. select * from t_test where a = 1 and b >=2 and c = 3;
  4. select * from t_test where c = 1 and a = 2 and b = 3;
  5. select * from t_test where a = 2 and c = 3;
  6. select * from t_test where b = 2 and c = 3;
  7. select a,b from t_test where a = 1 and b > 2;

解答:

  1. 遵循最左匹配原则,abc都能走索引,查询方式是在联合索引找到主键后,回主键索引查找完整数据。
  2. 根据最左匹配原则,ab可以走,c无法走,但是c可以索引下推(MySQL 5.6优化)
  3. abc都可以走,此处b>=2也是可以走索引的,且不会破坏c
  4. abc都能走,where查询条件字段的顺序不会影响,MySQL优化器会帮我们调整字段的查询顺序。
  5. a能,c不能走索引,但是c能索引下推。
  6. bc都不能,但是bc能走索引下推
  7. ab都能走索引,且查询方式是索引覆盖(不用回表)

where a > 1 and b = 2 and c < 3怎么建立索引?

  • 如果是idx_ab、idx_ac、idx_abc、idx_acb,则abc都不走索引
  • 如果是idx_ca、idx_cb、idx_cab、idx_cba,则abc都不走索引
  • 如果是idx_ba,则b和a走索引。
  • 如果是idx_ca,则b和c走索引。
  • 如果是idx_bac,则b和a走索引,c不走索引但走索引下推。
  • 如果是idx_cab,则b和c走索引,b不走索引但走索引下推。

where a > 100 and b = 100 and c = 123 order by d怎么建立联合索引?

推荐idx_bcda或者使用idx_cbda,这样的话bcd都能走索引(不会发生filesort),而a能走索引下推。

如果把a和d的位置互换,比如idx_bcad,虽然bca能走索引,但是在order by中的d就走不了索引(会发生file sort)。

select * from t where a > ? and b = ? or c = ?怎么优化?

分析:如果有or,那么光是索引idx_ba还是会导致索引失效。

所以应该使用索引idx_ba和idx_c,这样索引不会失效。

联合索引(a,b,c),select * from t where a = ? and b in (?,?) and c = ?会不会走索引?如果走,具体是哪些字段能走?

如果是in (?,?),并没有绝对的答案说走不走索引,如果b的值有3个(1、2、3),如果b = 1或b = 2的个数很少,那么b in (1,2)是会走索引的,但是如果b = 1或b = 2的个数很多(几百万条),那么b not in (1,2)就不会走索引(节省回表开销)。