Java开发 - 数据库索引的数据结构

目录

前言

题外话

什么是索引

索引的使用场景

索引的失效瞬间

索引的数据结构

Tree

BTree

B+Tree 

索引提高查询效率的原因

索引的分类

innodb的特点

聚簇索引

非聚簇索引

索引操作

创建索引

查询索引

删除索引

外键

结语


前言

上一章中讲解了慢sql优化的方式,其中索引并没有着重去说,那是因为索引内容特殊,它的数据结构复杂,足够我们单独开一篇来着重讲讲,这一篇将带领大家补齐上一章中关于索引优化的部分,也将对索引的底层数据结构做一个深度的剖析,希望大家喜欢。

题外话

之所以拖了这么久才开更是因为前几天🐑了,躲得过初一,躲不过十五,到今天才多少缓过来点劲,又重感冒加身,这哪是普通的发烧?简直是脱层皮啊。实际上我只烧了一天两夜,也就第一天不太起得来,第二天之后就是虚弱,时刻都能睡着,嗓子不怎么痛,就是咳嗽,到今天已经好很多了,一直出虚汗导致着凉又重感冒,999伺候吧。大家还是要照顾好自己。

什么是索引

索引是作用在数据库表的列上,用于对该列的数据进行排序,形成一个目录,从而提高查询效率的东西,索引比较适合数据量比较大的表中。

索引的使用场景

  • 大数据量使用,因为索引的创建也需要一定的时间;
  • 给需要作为查询条件的字段设置索引;
  • 给不需要频繁修改的字段设置索引,若是经常修改,会导致索引重建,也会浪费时间;
  • 索引不是越多越好,一般我们建议最多也就五六个吧。

索引的失效瞬间

索引的存在是为了帮助我们快速查询到数据,如果由于操作不当导致了全表扫描,那么索引就失去了其作用。接上一篇,所以我们使用sql的时候要避免这么做:

  • 避免在where子句中对数据进行判null操作,这将导致引擎放弃使用索引而转而使用全表扫描,比如:where name is null 或者is not null;
  • 避免在where子句中使用!=操作;
  • 避免在where子句中使用or作为连接条件,比如where name=xxx or age=xxx,这会导致如果其中一个字段没有索引,其他有索引的字段也不会使用索引;
  • 谨慎使用not in,in不会导致索引失效,not in(xxx,xxx,xxx,......)因为范围太大,索引就会失效,进行全表扫描;
  • 避免在where子句当中使用like模糊查询,这里仅限左模糊(like '_%');
  • 避免在where子句中使用表达式,比如:select ...... from table where age+4>20;
  • 避免在where子句中使用函数表达式,比如:select ...... from ...... where round(money)=....

索引可以提高select的效率,但也会拉低insert和update的效率,因为insert和update可能导致索引重建。

索引的数据结构

索引的底层采用的是B+Tree

B+Tree又是基于BTree的

Tree

Tree就是树,顾名思义,像树一样的结构,只要是树,就一定拥有四个属性:

  • 根结点:位于顶端且仅有一个
  • 高度:整个树的层次
  • 度:树中最大子节点数
  • 叶子结点:位于最底层且没有子节点,或者说度为0的节点

BTree

我不能告诉你什么是BTree,因为其本质上就是一个Tree,BTree有如下特点:

  • 以数据块来保存元素,并实现排序;
  • 每个数据块中保存的最大数据量为(度-1),如果数据量达到度值,进行分裂提取,将中间值进行提取,原数据块分裂为左右两个数据块;
  • 每次比较可通过中间值排除相当一部分的数据,树有几层,则排除几次,查询效率大大提高。

我们可以通过下图来查看BTree的数据结构大概长什么样:

指的注意的是,提取时,被提取的元素是中间值,且不会再存在于原数据块中(和B+Tree有区别),这些关键点要牢记。

B+Tree 

和BTree相比,B+Tree的查询效率要更高,我们不妨来看看B+Tree和BTree有哪些不同吧:

  • 叶子数据块之间通过单向链表进行连接,目的是为了提高区域查询效率(举个例子,比如查询相近的数据,比如查询0007和0011,查到0007之后,可以直接通过链表查询到下一个叶子结点找到0011,比重新中根结点开始查0011的效率要更高);
  • 叶子数据块进行分裂提取的时候,被提取的数据依然位于叶子数据块中,但如果是非叶子数据块,则向上提取时不会继续保留到原数据块中,这是因为所有的数据都已经存在于叶子数据块中,非叶子数据块这时候只是起到了目录的作用,保证要查询的数据位于叶子结点中,这一点和BTree不太一样,BTree不一定查询到叶子结点。
  • 查询次数减少,B+Tree我只关心叶子结点的数据,其他结点都只作为目录使用。

来看看B+Tree的数据结构:

和BTree很相似,但是其叶子结点间互相通过单向链表进行连接, 原因上面已经给出,可以看到,被从叶子提取出去的元素依然位于原模块中,非叶子结点则不会,这些被提取出去的节点就成了目录的效果,最终查询一定会到叶子结点。

索引提高查询效率的原因

数据库的索引都是保存在磁盘中的,且默认高度为3,度根据数据的量进行调整。所以查询的过程是IO查询。

当给数据库中某列添加一个索引的时候,索引就会对该列的数据进行排序,创建B+Tree目录,查询时根据该列进行查询时会通过B+Tree进行查询。

因为树的默认高度为3,所以最终都会进行3次的IO查询,第一次对根结点,第二次对子节点,第三次对叶子结点。这里要注意一点,每一次查询完都会将查到的数据缓存内存中进行多次的IO操作,这是因为在内存中查询效率更高,而每一个结点存在的数据不止一条。第三次缓存后在内存中查找具体的数据将最终得到想要的值。

注意:在内存中进行查询时采用的是二分查找法,而不是遍历。

虽然索引的底层是B+Tree,但是索引对B+Tree进行了优化:在叶子数据块中保存的元素不是一个元素值,而是以key-value的形式存在。至于key和value分别保存什么,要看索引的分类。

索引的分类

  • 聚簇索引/聚集索引:给主键id添加的索引
  • 非聚簇索引/非聚集索引:给非主键字短添加的索引

在说这两个索引之前,需要先讲一下innodb的特点,方便理解。

innodb的特点

我们知道,从mysql5.5开始,引擎被替换成了innodb:

  • innodb支持事务和行锁,上一篇有提到行锁;
  • 默认给主键添加聚簇索引,如果没有设置主键,会自动添加隐藏主键,类行为long,长度为6,并添加聚簇索引;
  • 除聚簇索引,innodb默认会给添加了unique约束以及外键约束的字段添加索引。

聚簇索引

聚簇索引由innodb默认添加,不需要开发者手动添加,这一点我们已经很清晰了。

聚簇索引中key和value分别是什么?

  • key为主键id;
  • value为id对应的整行数据。

这也是为什么通过id可以快速查找到整行数据的原因。

非聚簇索引

非聚簇索引需要开发者手动添加。

非聚簇索引中key和value分别是什么?

  • key为那一列的值;
  • value为该行数据对应的id的值

所以,非聚簇索引是先通过key找到主键的id的值,再通过id的聚簇索引找到整行数据,以达到快速查找数据的目的。我们把这一过程叫做“回表操作”。

索引操作

创建索引

create index index_name on table_name(col)
案例: 给字段添加unique约束,验证是否Innodb默认给添加了索引
添加unique约束:
已有表:alter table table_name add xxx type unique;
创建表:create table table_name(id int primary key,name varchar(50) unique)

对主键进行操作,可以尝试下,看是否会报错,报错则说明默认添加了索引,自行尝试。

查询索引

show index from table

删除索引

drop index index_name on table

外键

前面提到了外键,外键产生的原因是:一张表中的非主键字段来源于其他表中的主键字段的值,这就叫外键,是外键,就可以添加外键约束。

外键约束需要手动添加,被添加了外键约束的字段有个特点:这个被作为外键的主键字段原表中的那条数据不可被删除。

结语

这些东西没什么捷径可言,全凭经验。所以有时候你会看到工作好几年的人还是会犯同样的错误,这并不可怕,多练习,多总结,你现在踩过的坑终将成为你未来路上的垫脚石。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodingFire

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值