第36天 索引(二叉树,平衡二叉树,b树,b+树)innodb存储引擎索引 索引的简单使用 联合索引

什么是索引?
    索引是存储引擎中的一种数据结构,或者说是数据的组织方式,又称之为键key
    为数据建立索引就好比是为书建目录
    
为何要用索引?
    为了优化查询效率
    ps:创建完索引后会降低增、删、改的效率
    好就好在读写比例10:1

如何正确看待索引?
    开发人员最懂业务,任何一个软件都有其吸引用户的亮点
    亮点背后对应的是热数据,这一点开发人员是最清楚的,
    开发人员最了解热数据对应的数据库表字段有哪些,所以
    应该在开发软件的过程中就提前为相应的字段加上索引,而不是
    等到软件上线后,让DBA发现慢查询sql后再做处理,因为
    1、一个软件慢会影响用户体验,但是慢的原因有很多,你不能立即确定
    是sql的问题,所以等到定位到sql的问题,可能已经过去了很久,问题已经被
    拖了很久了
    2、因为大多数DBA都是管理型DBA而非开发型,所以即便是DBA从日志中看到了慢查询sql,
    也会因为其不懂业务而很难分析出慢的原因,最后这顶锅还是得扣在你开发的脑袋上,
    躲得过初一躲不过十五啊
索引到底是一种什么样的数据结构:B+树
    二叉树 平衡二叉树 B树 -》B+树
    小于节点在左  大于节点在右  
    平衡二叉树:要求每个节点的左右子树的高度不能超过1
    
    B树相对于平衡二叉树的优点:每个节点存储的数据量更多了,降低了树的高度
    
    B+树与B树的区别:只有叶子节点才放完整的数据,根节点与枝节点只放索引值,
    这样减少了每个节点硬盘空间的占用,可以使每个节点能存储更多的索引数据,
    从而降低了树的高度
    
    表--》书
    记录--》一页内容

    索引-》书的目录

索引就是b+树的模式

b+树的优点:
	b+树的高度是最低的,因此查询的效率是最高的
	
	b树与b+树查询范围sql语句的差别:
	select * from user where id > 12 and id < 15;
	b+树的叶子节点都是排好序的,这就意味着在范围查询上,b+树比b树更快,
	快就快在一旦找到了一个树叶节点,就不需要再从根节点查起了

二叉查找树
在这里插入图片描述
从图中可以看到,我们为user表(用户信息表)建立了一个二叉查找树的索引。图中的圆为二叉查找树的节点,节点中存储了键(key)和数据(data)。键对应user表中的id,数据对应user表中的行数据。二叉查找树的特点就是任何节点的左子节点的键值都小于当前节点的键值,右子节点的键值都大于当前节点的键值。 顶端的节点我们称为根节点,没有子节点的节点我们称之为叶节点。
如果我们需要查找id=12的用户信息,利用我们创建的二叉查找树索引,查找流程如下:

1、将根节点作为当前节点,把12与当前节点的键值10比较,12大于10,接下来我们把当前节点>的右子节点作为当前节点。

2、继续把12和当前节点的键值13比较,发现12小于13,把当前节点的左子节点作为当前节点。

3、把12和当前节点的键值12对比,12等于12,满足条件,我们从当前节点中取出data,即id=1>2,name=xm。

利用二叉查找树我们只需要3次即可找到匹配的数据。如果在表中一条条的查找的话,我们需要6次才能找到。

    select * from user where id = 12;   # 命中索引   
    select * from user where name = "xxx";  # 没有命中索引

平衡二叉树
在这里插入图片描述
这个时候可以看到我们的二叉查找树变成了一个链表。如果我们需要查找id=17的用户信息,我们需要查找7次,也就相当于全表扫描了。 导致这个现象的原因其实是二叉查找树变得不平衡了,也就是高度太高了,从而导致查找效率的不稳定。 为了解决这个问题,我们需要保证二叉查找树一直保持平衡,就需要用到平衡二叉树了。
平衡二叉树又称AVL树,在满足二叉查找树特性的基础上,要求每个节点的左右子树的高度不能超过1。 下面是平衡二叉树和非平衡二叉树的对比:
在这里插入图片描述
由平衡二叉树的构造我们可以发现第一张图中的二叉树其实就是一棵平衡二叉树。平衡二叉树保证了树的构造是平衡的,当我们插入或删除数据导致不满足平衡二叉树不平衡时,平衡二叉树会进行调整树上的节点来保持平衡。具体的调整方式这里就不介绍了。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。

B树
因为内存的易失性。一般情况下,我们都会选择将user表中的数据和索引存储在磁盘这种外围设备中。但是和内存相比,从磁盘中读取数据的速度会慢上百倍千倍甚至万倍,所以,我们应当尽量减少从磁盘中读取数据的次数。 另外,从磁盘中读取数据时,都是按照磁盘块来读取的,并不是一条一条的读。 如果我们能把尽量多的数据放进磁盘块中,那一次磁盘读取操作就会读取更多数据,那我们查找数据的时间也会大幅度降低。 如果我们用树这种数据结构作为索引的数据结构,那我们每查找一次数据就需要从磁盘中读取一个节点,也就是我们说的一个磁盘块,我们都知道平衡二叉树可是每个节点只存储一个键值和数据的。那说明什么?说明每个磁盘块仅仅存储一个键值和数据!那如果我们要存储海量的数据呢?可以想象到二叉树的节点将会非常多,高度也会及其高,我们查找数据时也会进行很多次磁盘IO,我们查找数据的效率将会极低!
在这里插入图片描述
为了解决平衡二叉树的这个弊端,我们应该寻找一种单个节点可以存储多个键值和数据的平衡树。也就是我们接下来要说的B树。
B树(Balance Tree)即为平衡树的意思,下图即是一颗B树。

注意:
– 图中的p节点为指向子节点的指针,二叉查找树和平衡二叉树其实也有,因为图的美观性,被省略了。 – 图中的每个节点称为页(一页占用内存16k),页就是我们上面说的磁盘块,在mysql中数据读取的基本单位都是页,所以我们这里叫做页更符合mysql中索引的底层数据结构。

从上图可以看出,B树相对于平衡二叉树,每个节点存储了更多的键值(key)和数据(data),并且每个节点拥有更多的子节点,子节点的个数一般称为阶,上述图中的B树为3阶B树,高度也会很低。 基于这个特性,B树查找数据读取磁盘的次数将会很少,数据的查找效率也会比平衡二叉树高很多。
假如我们要查找id=28的用户信息,那么我们在上图B树中查找的流程如下:

  1. 先找到根节点也就是页1,判断28在键值17和35之间,我们那么我们根据页1中的指针p2找到页3。

  2. 将28和页3中的键值相比较,28在26和30之间,我们根据页3中的指针p2找到页8。

  3. 将28和页8中的键值相比较,发现有匹配的键值28,键值28对应的用户信息为(28,bv)。

注意: – B树的构造是有一些规定的,但这不是本文的关注点,有兴趣的同学可以令行了解。 – B树也是平衡的,当增加或删除数据而导致B树不平衡时,也是需要进行节点调整的。

B+树
B+树是对B树的进一步优化。让我们先来看下B+树的结构图:
在这里插入图片描述
根据上图我们来看下B+树和B树有什么不同。

1、B+树非叶子节点上是不存储数据的,仅存储键值,而B树节点中不仅存储键值,也会存储数据。之所以这么做是因为在数据库中页的大小是固定的,innodb中页的默认大小是16KB。如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的IO次数有会再次减少,数据查询的效率也会更快。另外,B+树的阶数是等于键值的数量的,如果我们的B+树一个节点可以存储1000个键值,那么3层B+树可以存储1000×1000×1000=10亿个数据。一般根节点是常驻内存的,所以一般我们查找10亿数据,只需要2次磁盘IO。

2、因为B+树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的。那么B+树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而B树因为数据分散在各个节点,要实现这一点是很不容易的。

innodb存储引擎索引

innodb存储引擎索引分类:
    1 hash索引(key:value的形式)
        更适合等值查询,不适合范围查询
        
    2 B+树索引
        聚集索引/聚簇索引-》以主键字段的值作为key创建的索引
        辅助索引:针对非主键字段创建的索引(一张表中可以有多个,辅助查询可以直接查询到非主键字段创建的索引和key索引)
            
    innodb-》索引组织表
    回表查询:通过辅助索引拿到主键值,然后再回到聚集索引从根再查一遍
    覆盖查询:不需要回表就能拿到你想要的全部数据
    
    select name,age,gender from user where id = 3;  # 聚集索引查询
    select name,age,gender from user where name = "nana";   # 辅助索引查询,且也是回表查询
    select name,id from user where name = "nana";   # 覆盖查询

索引的简单使用

   create table t1(
        id int,
        name varchar(10),
    );
    
    create index id_xx on t1(id);   # 创建索引
    drop index id_xx on t1;     # 删除索引
    

测试索引提速代码

创建一个数据量比较大的表s1
#1. 准备表
create table s1(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);

#2. 创建存储过程,实现批量插入记录
delimiter $$ #声明存储过程的结束符号为$$
create procedure auto_insert1()
BEGIN
    declare i int default 1;
    while(i<3000000)do
        insert into s1 values(i,'nana','female',concat('nana',i,'@beautiful_girl'));
        set i=i+1;
    end while;
END$$ #$$结束
delimiter ; #重新声明分号为结束符号

#3. 查看存储过程
show create procedure auto_insert1\G 

#4. 调用存储过程
call auto_insert1();
250条记录=》ibd文件的大小167M   
 
验证sql语句
select count(*) from s1 where id = 33333;   # 查询时间慢

create index id_xx on s1(id);
select count(*) from s1 where id = 33333;   # 加上索引后查询时间提速很明显

explain select count(*) from s1 where id = 33333;   # 查看查询计划,是否命中索引

select count(*) from s1 where id > 3;   # 查询时间慢,查询范围大
select count(*) from s1 where id > 3 and id < 700;    # 查询时间快,范围比较小
select count(*) from s1 where id = 3;   # 查询时间快
结论:命中索引也不一定能起到很好的提速效果
 总结:
        1.应该对区分度高且占用空间比较小的字段建索引
        
        2.针对范围查询中了索引,如果范围很大,查询效率依然很低,如何解决
            要么把范围缩小
            要么就分段取值,一段一段取最终把大范围取完
            
        3.索引下推技术(默认开启)
            mysql会自动分析用哪个索引查询会更快
            
        4.不要查询字段放到函数或者参与运算
        select count(*) from where id*12 = 3;
        select count(*) from where id = 3/12;
        
        5.索引覆盖
            运行效率是最高
            
        6.联合索引
            最左前缀匹配原则
            把多个值匹配到一起,建立联合索引
            假设联合索引中建了两个辅助索引name和gender,可以命中name或者是name+gender,无法单独命中gender
        create index idx_id_name_gender on s1(id,name,gender);  
        id
        id name
        id gender
        id name gender

猜你喜欢

转载自blog.csdn.net/Yosigo_/article/details/114026256