对于innodb索引,采用是的B+tree的数据结构,及索引和数据都存储在一个文件中*.db;而不像MYISAM索引和数据是分开存储的.

举例说明,下面是students表,id是主键,name上有辅助索引,有6行数据记录。

  • 一级索引(聚簇索引)

  上图是InnoDB主键索引的B+tree,叶节点包含了完整的数据记录,像这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL会优先自动选择一个可以唯一标识数据记录的列作为主键,比如唯一索引列,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,长度为6个字节,类型为longint。

  • 二级索引(非聚簇索引)

对于secondary index,非叶子结点保存的是索引值,比如上面的name字段。叶子结点保存的不再是数据记录了,而是主键值。

  • innodb索引总结

MySQL聚集索引使得按主键的搜索非常高效的。
辅助索引需要搜索两遍索引:
         第一:检索辅助索引获得主键值

         第二:用主键值到主键索引中检索获得记录

为什么Innodb表需要主键?

     1)innodb表数据文件都是基于主键索引组织的,没有主键,mysql会想办法给我搞定,所以主键必须要有;
     2)基于主键查询效率高;
     3)其他类型索引都要引用主键索引;

为什么不建议Innodb表主键设置过长?

  因为辅助索引都保存引用主键索引,过长的主键索引使辅助索引变得过大;
  • innodb对B-TREE的改进

在上面的例子中:将下面数字插入到一棵5阶B-Tree中:[3,14,7,1,8,5,11,17,13,6,23,12,20,26,4,16,18,24,25,19]

插入这些无序数据一共经历了6次分裂,对于磁盘索引文件而言,每次分裂都是很昂贵的操作;

如果将以上数据排好序,再次插入是不是效果会好,我试验了下,虽然每次都是插入到最右结点,涉及迁移数据量会少,但是分裂的次数依然挺多,需要7次分裂。

每次分裂都是按照50%进行,这样存在明显的缺点就是导致索引页面的空间利用率在50%左右;而且对于递增插入效率也不好,平均每两次插入,最右结点就得进行一次分裂。那Innodb是如何进行改进的呢?

Innodb其实只是针对递增/递减情况进行了改进优化,不再采用50%的分裂策略,而是使用下面的分裂策略:

对于递增/递减索引插入操作:
1、插入新元素,判断叶子结点空间是否足够,如果足够,直接插入
2、如果叶子结点空间满了,判断父结点空间是否足够,如果足够,将该新元素插入到父结点中;如果父结点空间满了,则进行分裂。

比如下面一棵5阶B+Tree:

现在连续插入10,11,14,15,17,采用优化后分裂策略的分步图例如下:

【第一步】:插入10

 

由于最右结点还有空间,直接插入即可。

【第二步】:插入11

 

 

插入11时,由于最右结点空间已满,如果使用50%分裂策略,则需要分裂操作了,但是使用优化后的分裂策略,当该结点空间已满,还要判断该结点的父结点是否满了,如果父结点还有空间,那么插入到父结点中,所以11插入到父结点中了,同时形成一个子结点。

 【第二步】:插入14,15,17

 

优化后的分裂策略仅仅针对递增/递减情况,显著的减少了分裂次数并且大大提高了索引页面空间的利用率。

如果是随机插入,可能会引起更高代价的分裂概率。所以InnoDB存储引擎会为每个索引页维护一个上次插入的位置变量,以及上次插入是递增/递减的标识。InnoDB能够根据这些信息判断新插入数据是否满足递增/递减条件,若满足,则采用改进后的分裂策略;若不满足,则进行50%的分裂策略。

为什么建议InnoDB表主键是单调递增?

如果InnoDB表主键是单调递增的,可以使用改进后的B+tree分裂策略,显著减少B-Tree分裂次数和数据迁移,从而提高数据插入效率。不仅如此,它还大大提高索引页空间利用率。


 参考文章:

     http://database.51cto.com/art/201107/275030_1.html

     http://www.2cto.com/database/201411/351106.html

     http://hedengcheng.com/?p=525

     https://www.cnblogs.com/mysql-dba/p/6689597.html