1.什么是索引
索引就是便于高效查询数据而排好序的数据结构。它们包含着对数据表里所有记录的引用指针
索引存储在磁盘文件中
索引的文件存储形式与存储引擎有关(
InnoDB 存储引擎
MyISAM 存储引擎
MEMORY存储引擎
)
索引文件的数据结构是 B+树
2.B+树的由来
如果一个节点下不再有子节点,则为叶子节点。
a> 二叉查找树 特点
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉查找树。
二叉查找树的特点就是为了保证每次查找都可以折半而减少IO次数,但是二叉树就很考验第一个根节点的取值,因为很容易在这个特点下出现我们不想发生的情况“树不分叉了”,这是我们不想见到的。
b>二叉平衡树
平衡二叉树是采用二分法思维,平衡二叉查找树除了具备二叉树的特点,最主要的特征是树的左右两个子树的层级最多相差1。在插入删除数据时通过左旋/右旋操作保持二叉树的平衡,不会出现左子树很高、右子树很矮的情况。
使用平衡二叉查找树查询的性能接近于二分查找法,时间复杂度是 O(log2n)。查询id=6,只需要两次IO。
就这个特点来看,可能各位会觉得这就很好,可以达到二叉树的理想的情况了。然而依然存在一些问题:
时间复杂度和树高相关。
树有多高就需要检索多少次,每个节点的读取,都对应一次磁盘 IO 操作。树的高度就等于每次查询数据时磁盘 IO 操作的次数。磁盘每次寻道时间为10ms,在表数据量大时,查询性能就会很差。(1百万的数据量,log2n约等于20次磁盘IO,时间20*10=0.2s)
平衡二叉树不支持范围查询快速查找,范围查询时需要从根节点多次遍历,查询效率不高。
c>B树
这种数据结构我们称为B树,B树是一种多叉平衡查找树,主要特点如下:
1.B树的节点中存储着多个元素,每个内节点有多个分叉。
2.节点中的元素包含键值和数据,节点中的键值从大到小排列。也就是说,在所有的节点都储存数据。
3.父节点当中的元素不会出现在子节点中。
4.所有的叶子结点都位于同一层,叶节点具有相同的深度,叶节点之间没有指针连接。
到这里,B树已经是比较理想的了,但我们还有可以优化的地方:
缺点:
1.B树不支持范围查询的快速查找,如果要查找10和35之间的数据,查找到15之后,需要回到根节点重新遍历查找,需要从根节点进行多次遍历,查询效率有待提高。
2.如果data存储的是行记录,行的大小随着列数的增多,所占空间会变大。这时,一个页中可存储的数据量就会变少,树相应就会变高,磁盘IO次数就会变大。
d> B+树
B+树和B树最主要的区别在于非叶子节点是否存储数据的问题
- B树:非叶子节点和叶子节点都会存储数据。
- B+树:只有叶子节点才会存储数据,非叶子节点至存储键值。 叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。
B+树的最底层叶子节点包含了所有的索引项。从图上可以看到,B+树在查找数据的时候,由于数据都存放在最底层的叶子节点上,所以每次查找都需要检索到叶子节点才能查询到数据。所以在需要查询数据的情况下每次的磁盘的IO跟树高有直接的关系,但是从另一方面来说,由于数据都被放到了叶子节点,所以放索引的磁盘块锁存放的索引数量是会跟着增加的,所以相对于B树来说,B+树的树高理论上情况下是比B树要矮的。也存在索引覆盖查询的情况,在索引中数据满足了当前查询语句所需要的全部字段数据,此时只需要找到索引即可立刻返回,不需要检索到最底层的叶子节点。
3.聚簇索引 非聚簇索引
聚簇索引首先并不是一种索引类型,而是一种数据存储方式,
是否为聚簇索引指的是b+树的具体实现方式,也就是每个节点的data域里面到底放什么东西。究竟放的是具体的数据,还是指向数据的“指针”?
先给出一个结论:InnoDB存储引擎的主键使用的是聚簇索引,
非主键使用的称作:“辅助索引”、“二次索引”,
而MqISAM存储引擎无论主键,还是非主键使用的索引都是一样的:“非聚簇索引”。
4.避免回表 或 覆盖索引
InnoDB存储引擎支持覆盖索引,即从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录
前知:辅助索引在他的b+树的叶子节点的data域保存了行的主键值,因此这种索引方式都需要“二次查找”,也就是说先通过辅助索引首先找到的主键值,再通过主键值去聚簇索引中查找对应的行数据。
在InnoDB的存储引擎中,使用辅助索引查询的时候,因为辅助索引叶子节点保存的数据不是当前记录的数据而是当前记录的主键索引,索引如果需要获取当前记录完整数据就必然需要根据主键值从主键索引继续查询。这个过程我们成位回表。想想回表必然是会消耗性能影响性能。那如何避免呢?
使用索引覆盖,举个例子:现有User表(id(PK),name(key),sex,address,hobby…)
如果在一个场景下,select id,name,sex from user where name ='zhangsan';这个语句在业务上频繁使用到,而user表的其他字段使用频率远低于它,在这种情况下,如果我们在建立 name 字段的索引的时候,不是使用单一索引,而是使用联合索引(name,sex)这样的话再执行这个查询语句是不是根据辅助索引查询到的结果就可以获取当前语句的完整数据。这样就可以有效地避免了回表再获取sex的数据。
这就是一个典型的使用覆盖索引的优化策略减少回表的情况。
自己理解 索引在数据表中的存储格式:
Mysql中的每一个索引都是一颗B+树来存储的
所有表都有主键(没有时系统会默认创建一个):
主键的B+tree是聚簇索引,即树的数据(节点)按照主键顺序存放。
非叶子节点存放主键值
同时叶子节点中存放的即为表的行记录数据,也将聚集索引的叶子节点称为数据页,
每个数据页都通过一个双向链表来进行链接
非主键的索引就是辅助索引,即树的数据(节点)按照其他键值顺序存放。
非叶子节点存放主键值
树的叶子节点上是不存放真实数据的,包含辅助索引键 和 聚簇索引的索引键
每个数据页都通过一个双向链表来进行链接
每次使用辅助索引检索都要经过两次B+树查找(一次辅助索引的查找到索引值,和一次聚簇索引查找到真实的数据)
覆盖索引
使用索引列覆盖要查询的字段,如果查询条件使用的是普通索引(或是联合索引的最左原则字段),查询结果是索引的列或是主键,不用回表操作,直接返回结果,减少IO磁盘读写读取正行数据