Oracle学习笔记(一)——B-Tree索引
https://blog.csdn.net/biww620/article/details/73003880
目录是索引的一个最好的例子,每条目录包含对应章节的标题和页码,类比索引的每条索引项包含了数据记录的某些键值组合并包含了对应数据块的访问路径(rowid)。目录的存在就是为了快速定位到感兴趣的内容,索引的存在也是问了加快对表数据的随机访问。
常常被提及的索引可能有单键索引、组合索引、唯一索引、B-Tree索引、位图索引、函数索引、全局索引、局部索引等等。这里只是列举出镜率较高的索引类型,并没有去做严格的划分,各类型间有重叠,比如函数索引可以是B-Tree索引也可以是位图索引。在Oracle中索引和表一样属于逻辑结构中的段(segment)。每个索引都拥有独立的结构,无论是从物理结构还是逻辑结构来看与其所关联的表完全分开,即便索引失效也不会造成原有SQL无法执行,只是改变了执行计划,降低了执行效率。
B-Tree索引
查找树有完全二叉树、二叉查找树、平衡二叉树、红黑树,B-Tree,B+-Tree,B*-Tree等。对于二叉树其目的是要将查询复杂度控制在O(lgN)以内。(注:这里的lgN表示log2N),查询效率与树的高度有关。在少量数据构造的二叉树查询是很高效的,但是在数据库应用中,数据量巨大,如果构造二叉树那么树的高度将也很巨大,势必增加读取索引节点的I/O次数,影响查询效率。于是B-Tree挺身而出,在很大的数据量范围内能够保持B-Tree树的层级不会增加。
图片来自网络
从上图中可以看出在oraccle中B-Tree索引具有以下结构特点:
B-Tree索引包含根节点(Root Node)、分支节点(Branch Node)和叶子节点(Leaf Node)。
索引树高度一般都很低,上百亿记录的索引树的高度也只有5,6层。
索引本身有序。叶子节点是一个双向链表,因此可以按照索引的升序或降序进行索引扫描。
索引项包含键值信息和ROWID。索引项由索引头部、索引列的长度、索引值以及对应记录的rowid。其中唯一索引对应的rowid是唯一的,非唯一索引对应的rowid是可能有多个(多个rowid是有序的)。
索引列值全部为NULL的索引项是不会被记录的。
B-Tree索引简要分析
一、提高查询效率
200w条记录的表test_index_t1,查找条件col1 = 98765的记录没有索引的执行计划如下:
在test_index_t1表的col1列添加索引
create index index_col1 on test_index_t1(col1);
再次执行查询的执行计划如下:
未建立索引时执行计划是TABLE ACCESS FULL用时1100ms,建立索引后执行计划是INDEX RANGE SCAN用时90ms,效率提高了10倍以上。这里test_index_t1的数据量不大。如果是大数据量的表执行效率的差距会更加明显。
二、索引树高度较低
通过以下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操作,因此保持索引树高度较低对查询性能有很大的好处。
三、索引包含键值
索引包含索引键值,单键或键组合,如果查询所需的字段均在索引项中则可以避免回表读数,提高查询性能。创建表test_index_t1包含三个字段col1,col2,col3初始化为300w条记录,并建立了(col1,col2)组合索引。
create index index_col1_col2 on test_index_t1(col1, col2);
1. 执行sql
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;
从执行计划和统计信息中可以看出执行了排序过程并使用了内存空间。给test_index_t1表col1字段加上索引后的执行计划如下
执行计划走索引后SORT ORDER BY不存在了。因此,如果因为排序导致查询性能降低可以考虑在索引中包含需要排序的列,这样利用索引本身的有序性可以避免排序带来的性能损耗。
五、索引不保存索引键值全部为NULL的记录
这个特点跟count,sum/avg,max/min的执行计划息息相关,可以总结为以下两点:
COUNT/SUM/AVG必须在索引列为非空的情况下才可以走到索引。(建表是列指定为Not Null或为主键或在where条件中指明为is not null)。
MIN/MAX则不会受到空值的影响,均能走到索引。
表test_index_t1有300w条记录,在col1上建立了索引,执行:
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做验证。
————————————————
版权声明:本文为CSDN博主「biww620」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/biww620/java/article/details/73003880