分为两个部分回答:
- B+ Tree 的索引原理
- B+ Tree 索引相比其他索引类型的优势
相比 B Tree
B+ tree 只在叶子结点存储数据,而 B Tree 非叶子结点也要存储数据,所以 B+ Tree 的单个节点数据量更小,再相同的磁盘 IO 次数下,能查询更多的节点;B+ Tree 叶子结点采用的是双链表连接,适合 Mysql 中常见的基于范围的顺序查找,而 B Tree 无法做到这一点
相比二叉树索引结构
对于有 N 个叶子结点的 B+ Tree ,其搜索复杂度为 O(logdN),其中 d 表示节点允许的最大子节点个数为 d 个。在实际的应用中,d 值是大于 100 的,这样就保证了,即时数据达到千万级别时,B+ Tree 的高度依然维持在 3 4 层左右,也就是说一次数据查询操作只需要做 34 次的磁盘 IO 操作就能查询到目标
而二叉树的每个父节点的儿子节点个数只能是 2 个,意味着其搜索复杂度为 O(logN),这已经比 B+ Tree 高出不少,因此二叉树检索到目标数据所经历的磁盘 IO 次数要更多
相比 Hash 表结构
范围查询是 Mysql 中常见的场景,但是Hash 表不适合做范围查询,它更适合做等值的查询,这也是 B+ Tree 索引要比 Hash 表索引有更广泛的使用场景的原因
二、通过执行计划查看索引使用详情
对于执行计划,参数有 possible_key 字段标识可能用到的索引
key 字段表示实际使用的索引
key_len 表示索引的长度
rows 表示扫描的数据行数
常见扫描类型(type)的执行效率从低到高的顺序为
- ALL 全表扫描
- index 全索引扫描
- range 索引范围扫描
- ref 非唯一索引扫描
- eq_ref 非唯一索引扫描
- const 结果只有一条的主键或唯一索引
索引失效的情况
- 当 Mysql 优化器根据 field like *** 这个条件,到索引 index field 的 B+ tree 结果进行查询评估时,发现当前节点的左右子节点的值都有可能符合 条件,于是优化器判定当前索引需要扫描整个索引,并且还要回表,不如直接全表扫描
- 索引列上做了计算、函数、类型转换操作,这些情况下索引失效是因为查询过程需要扫描整个索引并回表,代价高宇全表扫描;like 匹配使用了前缀匹配符 '%abc';字符串不加引号导致类型转换
如果 Mysql 查询优化器预估走索引的代价比全表扫描的代价还要大,则不走对应的索引,直接全表扫描,如果走索引比全表扫描代价小,则使用索引
优化索引办法
前缀索引优化
- 前缀索引就是用某个字段中,字符串的前几个字符建立索引,比如我们可以在订单表上对商品名称字段的前五个字符建立索引。使用前缀索引是为了减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串作为索引时,使用前缀索引可以帮助我们减小索引项的大小
- 但是前缀索引有一定的局限性,例如 order by 就无法使用前缀索引,无法把前缀索引用作覆盖索引
覆盖索引优化
- 覆盖索引是指 SQL 中 query 的所有字段在索引 B+ Tree 的叶子结点上都能找得到的那些索引,从辅助索引中查询得到记录,而不需要通过聚簇索引查询获得。假设我们只需要查询商品的名称、价格、有什么方式可以避免回表呢
- 我们可以建立一个组合索引,即商品 ID,名称,价格作为一个组合索引。如果索引中存在这些数据,查询将不会再次检索主键索引,从而避免回表。所以使用覆盖索引的好处很明显,即不需要查询出包含整行记录的所有信息,也就减少了大量的 IO 操作
联合索引
- 联合索引时,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。比如联合索引(userpin ,username),如果查询条件是 userpin = 1 AND username = 2,就可以匹配上联合索引;或者查询条件是 WHERE userpin = 1也能匹配上联合索引,但是如果查询条件是 WHERE username = 1,就无法匹配上联合索引
- 另外,建立联合索引时的字段顺序,对索引效率也有很大影响。越靠前的字段被用于索引过滤的概率越高,实际开发工作中建立联合索引时,要把区分度大的字段排在前面,这样区分度大的字段越有可能被更多的 SQL 使用到
- 区分度 = distinct(column) / count(1)
- 区分度就是某个字段 column 不同值的个数除以表的总行数,比如性别的区分度就很小,不适合建立索引或者不适合排在联合索引的靠前的位置,而 uuid 这类字段就比较适合做索引或排在联合索引列靠前的位置