InnoDB学习(七)之索引结构

索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。可以将数据库索引和书的目录进行类比,通过书的目录我们可以快速查找到章节位置,如果没有目录就只能一页页翻书查找了。

索引数据结构

可以用于提升查询效率的索引结构很多,常见的有B树索引、哈希索引和B+树索引。接下来我们会对这些索引一一进行介绍,并说明InnoDB为什么采用B+树作为索引。

磁盘IO

文件是存储在硬盘上面的。当下硬盘的读取速度十分有限,所以在进行查询定位某个数据的时候,应该尽可能地减少磁盘I/O次数。

磁盘预读

由于存储介质的特性,磁盘本身存取就比主存慢很多,再加上机械运动耗费,磁盘的存取速度往往是主存的几百分之一,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。

局部性原理:CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。

预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页的大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。

合理利用磁盘预读

一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。

如果我们能合理使用磁盘预读的特性,使每次磁盘IO读到的页中的数据都是有用的,就可以大大提升数据的查询效率。

B树索引

B树可以看作是对二叉查找树的一种扩展,B树允许每个节点有M-1个子节点,B树有以下特点:

  1. 根节点至少有两个子节点;
  2. 每个节点包含M-1条数据,节点中的数据安装索引递增顺序排序;
  3. 节点中有最多有M个指针指向下一层节点,这些指针位于节点的多个数据之间,下一层节点的所有数据值大于指针左侧的数据,小于指针右侧的数据;
  4. 每个节点至少包含M/2条数据;

接下来我们用下表示例的用户数据来构建B树,如表所示,用户数据包含姓名、性别、年龄三个字段,我们把用户年龄作为数据库主键(假设年龄具有唯一性),那么构建出来的B树的结构如下图所示。

|||||||||||
|–|–|–|–|–|–|–|–|–|–|–|
|姓名|陈尔|张散|李思|王舞|赵流|孙期|周跋|吴酒|郑史|
|性别|男|男|女|女|男|男|男|女|男|
|年龄|5|10|20|28|35|56|25|80|90|

InnoDB学习(七)之索引结构

相比较与常见的二叉树,B树的一个节点中存放了更多的数据,这样做可以有效的减少一次数据查找过程中的磁盘IO次数:

  • 二叉树每个节点只存放一个数据,节点之间用指针关联,节点之间的空间是离散的,所以每个节点都对应一次磁盘IO,查找一次数据的IO次数为O($log_2$N);
  • B树的节点可以存放M-1个数据,如果这M-1个数据刚好可以放到一个页中,那么B树查找一次数据的IO次数为O($log_M$N);

哈希索引

哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。哈希表是一种以键-值(Key-Value)存储数据的结构,用户可以在O(1)时间复杂度内按照Key查找到对应的Value。

哈希表通常是一个数组,数据在数组中的位置可以按照索引的值安装哈希算法进行计算,如果两个数据的索引值计算出来的位置相同,那么通常可以采用链地址法解决冲突(其它解决地址冲突的方法还有开放定制法,链地址法,公共溢出区法,再散列法等)。

如下表数据所示,我们依旧按照用户的年龄为用户数据建立索引(假设用户年龄不会相同),我们采用的哈希算法为 addr=age%10,我们可以建立长度为10的数组作为哈希表,按照哈希函数一一把用户放入哈希表,按照用户年龄查找用户时,可以直接计算出用户所在的位置,从而得到用户信息,最终得到的哈希表以及查询流程如下图所示。

姓名 陈尔 张散 李思 王舞 赵流 孙期 周跋 吴酒 郑史 性别 男 男 女 女 男 男 男 女 男 年龄 5 10 20 28 35 56 25 80 90

InnoDB学习(七)之索引结构
哈希索引有以下优点:
  1. 占用的额外空间小,为数据新建一个哈希索引需要的额外空间为O(N),和索引字段长度无关;
  2. 查询速度极快,哈希函数合理的情况下,程序可以在O(1)的磁盘IO次数内查找到数据;

哈希索引有以下缺点:

  1. 无法进行范围查询,哈希过程中已经丢失了索引的顺序性;
  2. 无法对数据进行排序查找,比如查找年龄最大的用户;
  3. 无法使用部分索引查找,比如前缀查询等;
  4. 哈希函数不合理的情况下,会导致哈希冲突问题,造成查询效率变低;

B+树索引

InnoDB使用的索引的数据结构是B+树,数据库表定义中的每一个索引对应一颗B+树,默认的聚簇索引也是一颗B+树,B+树有以下特征:

  1. 所有节点关键字是按递增次序排列,并遵循左小右大原则;
  2. 非叶节点的子节点数在1到M之间(下图中M为3),空树除外;
  3. 非叶节点的索引数目大于等于ceil(M/2)个且小于等于M个;
  4. 所有叶子节点均在同一层,叶子节点之间有从左到右的指针;
  5. 数据存储在叶子节点,非叶子节点只存储索引;

接下来我们用几条示例的用户数据来构建B+树,如表所示,用户数据包含姓名、性别、年龄三个字段,我们把用户年龄作为数据库主键(假设年龄具有唯一性),那么构建出来的B+树的结构如下图所示。

姓名 陈尔 张散 李思 王舞 赵流 孙期 周跋 吴酒 郑史 性别 男 男 女 女 男 男 男 女 男 年龄 5 10 20 28 35 56 25 80 90

InnoDB学习(七)之索引结构

B+树索引数据结构有以下列出的几种优势:

  1. 查询性能稳定,查询一条数据需要的IO次数往往是树的高度次;
  2. 范围查询效率高,安装索引范围查询时,可以先查找的第一个满足要求的数据,然后向后遍历,直到第一个不满足条件的数据为止,中间的数据都符合要求;
  3. 查询效率高,往往一次数据查询只需要2~3次磁盘IO;
  4. 叶子节点存储所有数据,不需要去B+树之外找数据;

InnoDB为什么采用B+树

在InnoDB引擎中,我们为数据库创建的索引都是以B+树的形式存在,为什么InnoDB不采用哈希索引或者B树索引呢?主要是基于以下原因:

  • 数据库查询经常会出现非等值查询,哈希索引在这种情况下无法工作;
  • 相比于B树,B+树索引非叶子节点不存放数据,从而磁盘一次IO可以读取更多的索引数据,有效减少磁盘IO次数;
  • 数据库查询经常会出现范围查询,B+树底层的叶子节点之间按照顺序排列,可以更有效的实现范围查询;

自增主键

通过上文我们知道,B+树需要维护索引的有序性。

  1. 当用户向B+树插入数据,如果插入点对应的节点有空余位置,那么只需要挪动节点中的数据,并把需要插入的数据放入B+树即可;
  2. 当用户向B+树插入数据,如果插入点对应的节点没有空余位置,那么就需要生成一个新的节点,并把一部分数据挪过去;这种情况不仅会影响插入效率,由于分裂出来的节点只有部分数据,所以会导致空间的利用率降低;
  3. 当用户删除B+树中的数据时,如果节点或相邻节点的数据量很少,那么只需要直接删除数据,并按挪动节点中的其它数据即可;
  4. 当用户删除B+树中的数据时,如果节点和相邻节点的数据量很少,那么在删除之后,可能需要把节点和相邻节点合并,从而提高空间利用率;

基于B+树需要维护索引有序性的特点,我们对索引字段提出以下建议:

  1. 对于数据插入比较多的场景,主键索引字段最好是递增的。递增的主键每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。
  2. 主键索引的长度应当尽量小,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

在InnoDB中,我们应当尽量使用自增主键,自增主键有插入效率高、占用空间小等优势。

数据空洞与重建索引

数据空洞

当你对InnoDB进行修改操作时,例如删除一些行,这些行只是被标记为”已删除”,而不是真的从索引中物理删除了,因而空间也没有真的被释放回收。InnoDB的Purge线程会异步的来清理这些没用的索引键和行,但是依然没有把这些释放出来的空间还给操作系统重新使用,因而会导致页面中存在很多空洞。如果表结构中包含动态长度字段,那么这些空洞甚至可能不能被InnoDB重新用来存新的行,因为空间空间长度不足。

数据空洞带来的问题:

  1. 删除表中的数据后,表占用的空间不会变小,造成空间浪费;
  2. 会降低数据查询的速度,因为空洞会占用页空间;

我们可以通过以下SQL来查看数据库中的空洞大小,执行语句如下所示,返回结果中的DATA_FREE表示表中空闲数据块的大小。

select data_length,data_free from information_schema.tables where table_schema='test' and table_name='test';

InnoDB学习(七)之索引结构

重建索引

当一张表的索引中的数据空洞过多时,会影响SQL语句的执行效率,此时我们就需要清理这些数据空洞。

清理数据空洞比较好的办法是重建索引,因为重建索引的过程中,会按照索引的大小排序后建立索引,建立出来的索引比较紧凑。

有什么办法可以重建索引呢?我们比较直观的想法肯定是先删除索引,再重建索引。然而不论是删除主键还是创建主键,都会将整个表重建。所以连着执行这两个语句的话,第一个语句就白做了。

alter table user_info drop primary key;
alter table user_info add primary key(id);

InnoDB中可以通过以下转换数据引擎的语句来重建表的所有索引。这是因为在转换数据引擎(即使没有真正转换)的过程中,会读取表中所有的数据,再重新写入,这个过程中,会释放空洞。需要注意的是,通过这种方法重建索引耗时比较长。

alter table test engine=innodb

InnoDB学习(七)之索引结构

本文最先发布至微信公众号,版权所有,禁止转载!

Original: https://www.cnblogs.com/yuhushen/p/15762975.html
Author: 御狐神
Title: InnoDB学习(七)之索引结构

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/589314/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 大连交通大学课程共享

    如本页面访问适配不佳,阅读体验不好可访问公众号页面(适配更好)。公众号页面:https://mp.weixin.qq.com/s/5g2-Izrygm6WhKiT3z1yow 设立…

    Java 2023年6月15日
    067
  • SpringBoot 自动配置之Spring Data JPA

    前言 不知道从啥时候开始项目上就一直用MyBatis,其实我个人更喜欢JPA些,因为JPA看起来OO的思想更强烈些,所以这才最近把JPA拿出来再看一看,使用起来也很简单,除了定义E…

    Java 2023年5月30日
    082
  • 数据库

    1、数据库的基本概念 1.1 数据库是干什么的? 数据库(Database)是按照数据结构来组织、存储和管理数据的仓库。 每个数据库都有一个或多个不同的 API 用于创建,访问,管…

    Java 2023年6月7日
    0105
  • 经典面试题:==和equals的区别

    1.== 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址 2.equals的话,它是属于java.lang.Object类里面的方法,如…

    Java 2023年6月14日
    0106
  • nginx用户认证配置( Basic HTTP authentication)

    ngx_http_auth_basic_module模块实现让访问着,只有输入正确的用户密码才允许访问web内容。web上的一些内容不想被其他人知道,但是又想让部分人看到。ngin…

    Java 2023年5月30日
    081
  • (WebFlux)003、多数据源R2dbc事务失效分析

    一、背景 最近项目持续改造,然后把SpringMVC换成了SpringWebflux,然后把Mybatis换成了R2dbc。中间没有遇到什么问题,一切都那么的美滋滋,直到最近一个新…

    Java 2023年6月15日
    078
  • svn版本控制迁移到git

    因为导入到git需要配置原作者(svn提交人)和git账户的映射关系 其格式为: vim authors-transform.txt 新建一个目录作为 Git 项目的根目标,并进入…

    Java 2023年6月16日
    090
  • element-ui使用心得总结

    1、树形列表el-table的数据data中有hasChildren属性时,必须结合lazy,load,才能有点击展开的效果,缺其中一个要素没有配置,都不能点击展开; 2、树形列表…

    Java 2023年6月5日
    070
  • 如何正确的中断线程?你的姿势是否正确

    Java停止线程的逻辑(协同、通知) 在Java程序中,我们想要停止一个线程可以通过interrupt方法进行停止。但是当我们调用interrupt方法之后,它可能并不会立刻就会停…

    Java 2023年6月5日
    078
  • 修改requests_html.AsyncHTMLSessions使得支持url参数

    一、修改源代码 #重写AsyncHTMLSession中的run()方法 def run(self, *coros,urls=None): """ P…

    Java 2023年5月30日
    076
  • vue3 vite2 封装 SVG 图标组件-基于 vite 创建 vue3 全家桶项目续篇

    在《基于 vite 创建 vue3 全家桶》一文整合了 Element Plus,并将 Element Plus 中提供的图标进行全局注册,这样可以很方便的延续 Element U…

    Java 2023年6月16日
    067
  • 驼峰命名法

    驼峰命名法:单词首字母大写 所有变量名、方法名、类名都要求:见名知意 类名:AnimalDog 首字母大写然后驼峰命名规范:Animal Dog 单词首字母大写 类成员变量:首字母…

    Java 2023年6月6日
    0207
  • JAVA中json的转换

    首先需要导入alibaba的json包pom: com.alibaba fastjson 1.2.47 举例如下: Map转JSON Map map = new HashMap()…

    Java 2023年6月6日
    0106
  • mac(m1)配置my.cnf

    今天开始学习了数据库,在安装MySQL之后启动一直报错,然后在网上找了很多解决方法,最后用以下方法解决 对于习惯了windows的小伙伴来说,直接去安装目录里边修改my.ini就可…

    Java 2023年6月14日
    074
  • Java: Excel导入导出

    【相关文档】:EasyPoi教程 1. 依赖 <dependency> <groupid>cn.afterturn</groupid> <…

    Java 2023年5月29日
    0113
  • -2020年度钻石C++C(2)《博学谷》

    -2020年度钻石C++C(2)《博学谷》 第一类:数据类型关键字 void 声明函数无返回值或无参数,声明无类型指针,显式丢弃运算结果。 char 字符型类型数据,属于整型数据的…

    Java 2023年6月7日
    0114
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球