下面是简单的介绍了下b-tree在mysql数据库中作为索引的原理。
1.基础
2.类型:
3.1 B-Tree索引
3.1.1为什么使用的是b-tree而不是二叉树?
查找树有完全二叉树、二叉查找树、平衡二叉树、红黑树,B-Tree,B+-Tree,B*-Tree等。对于二叉树其目的是要将查询复杂度控制在O(lgN)以内。(注:这里的lgN表示log2N),查询效率与树的高度有关。在少量数据构造的二叉树查询是很高效的,但是在数据库应用中,数据量巨大,如果构造二叉树那么树的高度将也很巨大,势必增加读取索引节点的I/O次数,影响查询效率。于是B-Tree挺身而出,在很大的数据量范围内能够保持B-Tree树的层级不会增加。
3.1.2 b-tree的树结构是怎么存数据的?
如果下面看不懂的,请参考文章https://blog.csdn.net/qq_36098284/article/details/80178336
图片来自网络
从上图中可以看出在oraccle中B-Tree索引具有以下结构特点:
- B-Tree索引包含根节点(Root Node)、分支节点(Branch Node)和叶子节点(Leaf Node)。
- 索引树高度一般都很低,上百亿记录的索引树的高度也只有5,6层。(下面有分析)
- 索引本身有序。叶子节点是一个双向链表,因此可以按照索引的升序或降序进行索引扫描。(什么叫双向链表?)
- 索引项包含键值信息和ROWID。索引项由索引头部、索引列的长度、索引值以及对应记录的rowid。其中唯一索引对应的rowid是唯一的,非唯一索引对应的rowid是可能有多个(多个rowid是有序的)。
- 索引列值全部为NULL的索引项是不会被记录的。
- branch中存的是什么?
解释上图的结构和b-tree的查询原理:
下面b-tree的限制基本都是针对多列索引的情况:
如下部分是转载:
他这个文章主要对如下的几个特点进行了验证:(b-tree类型)
索引可以提交效率。
索引树的层数不是很高。
复合索引的何时使用该索引,以及存在回读的问题
索引本身是有序的,可以快速处理排序的操作
索引不保存索引键值全部为null的记录
在test_index_t1表的col1列添加索引
- create index index_col1 on test_index_t1(col1);
未建立索引时执行计划是TABLE ACCESS FULL用时1110ms,建立索引后执行计划是INDEX RANGE SCAN用时90ms,效率提高了10倍以上。这里test_index_t1的数据量不大。如果是大数据量的表执行效率的差距会更加明显。(1s = 1000ms)
二、索引树高度较低
通过以下sql可以查询索引的统计信息,可以看出树的高度即使在数据很大的情况下也不会很高。
其中BLEVEL表示索引树的高度,高度为BLEVEL + 1
- SELECT
- index_name,
- blevel,
- leaf_blocks,
- num_rows,
- distinct_keys,
- clustering_factor
- FROM
- user_ind_statistics
- WHERE
- table_name = UPPER('test_index_t1');
对于200w条记录的表test_index_t1执行索引统计信息查询后得到的结果为:
可以看出BLEVEL = 2也就是说索引树的高度为3。构建了记录数分别为10条,20w条和300w条的表并建立相同的索引,索引树高度分别为2,2,3。因此可以看出B-Tree索引的高度是比较低的,能够在大数据量的情况下保证树高度值很低。在通过索引执行查询时一个层级往往就代表一次I/O操作,因此保持索引树高度较低对查询性能有很大的好处。
三、索引包含键值
注意:下面的讨论是没有包含最左前缀的。就是使用的where都是col1列的索引,关于最左前缀的参考:https://blog.csdn.net/qq_36098284/article/details/80178051
索引包含索引键值,单键或键组合,如果查询所需的字段均在索引项中则可以避免回表读数,提高查询性能。创建表test_index_t1包含三个字段col1,col2,col3初始化为300w条记录,并建立了(col1,col2)组合索引。
- create index index_col1_col2 on test_index_t1(col1, col2);
- select col1 from test_index_t1 where col1 between 10 and 20;
2. 执行sql
- select col1, col2 from test_index_t1 where col1 between 10 and 20;
3. 执行sql
- select * from test_index_t1 where col1 between 10 and 20;
从上面三次查询结果可以看出:
(1) 三次执行SQL均用到了索引INDEX_COL1_COL2,索引执行方式为Index Range Scan
(2)
存在回读的问题。第一次和第二次查询(col1)、(col1、col2)均未回表读数,而第三次查询存在TABLE ACCESS BY INDEX ROWID回表读数,原因是组合索引INDEX_COL1_COL2中不包含列col3,因此通过索引扫描得到最终记录的rowid后还会根据rowid到表中读取col3。
总体来看,如果所需列包含于索引中那么可以通过索引避免回表读数从而提高查询性能。但需要注意的是索引本身也有性能消耗,并不是包含的列越多越好。一般建议索引列不超过3个,从实际的经验来看5,6个也还是可以接受。
四、索引本身有序
在前面提到的索引结构中可以看出索引叶子结点本身是按照索引键升序排列,相当于一个双向链表,可以进行升序或降序扫描。删除test_index_t1表的索引,再执行查询
- select col1, col2 from test_index_t1 where col1 between 10 and 20 order by col1;
执行计划走索引后SORT ORDER BY不存在了。因此,如果因为排序导致查询性能降低可以考虑在索引中包含需要排序的列,这样利用索引本身的有序性可以避免排序带来的性能损耗。
五、索引不保存索引键值全部为NULL的记录
这个特点跟count,sum/avg,max/min的执行计划息息相关,可以总结为以下两点:- COUNT/SUM/AVG必须在索引列为非空的情况下才可以走到索引。(建表是列指定为Not Null或为主键或在where条件中指明为is not null)。
- MIN/MAX则不会受到空值的影响,均能走到索引。
- select count(1) from test_index_t1;
可以看出是走了全表扫描。在where条件中增加col1 is not null后的执行计划为:
用INDEX FAST FULL SCAN的方式使用索引INDEX_COL1。最后col1添加属性not null后的执行计划为:
可以看出给列col1添加了not null属性后执行计划跟在where条件中指明is not null相同。这里不再对sum/avg,min/max做验证。
参考:https://blog.csdn.net/biww620/article/details/73003880
参考:https://blog.csdn.net/davidwang9527/article/details/2236841