热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

第一篇:MySQL索引数据结构演进之你心里有B树(数)吗

什么是索引在关系数据库中,索引是一种对数据库表中一列或多列的值进行排序的一种存储结构(数据结构),索引解决的问题是查询速度

什么是索引

在关系数据库中,索引是一种对数据库表中一列或多列的值进行排序的一种存储结构(数据结构),索引解决的问题是查询速度慢。容易产生理解偏差的点解释一下:


  • 索引是针对表来说的,不是针对数据库来说的(建表的sql语句中的index就是索引)。
  • InnoDB存储引擎不是MySQL自己的,而是Innobase Oy公司开发的,该公司后被甲骨文公司并购

索引的数据结构演进


链表结构

假设用户表中有字段id,name,password。我们需要对这个表进行查询。
image.png
我们知道数据都是存储在容器中的,假设现在上表中的数据存放在一个链表中,执行以下SQL可以发现影响查询速度的是这条数据在第几行。

select * from user where password = 33 ; //查找1次
select * from user where password = 36 ; //查找2次
select * from user where password = 1 ; //查找3次
select * from user where password = 26 ; //查找4次
select * from user where password = 9 ; //查找5次
select * from user where password = 47 ; //查找6次
select * from user where password = 5 ; //查找7次
select * from user where password = 99 ; //查找8次
select * from user where password = 52 ; //查找9次

假设表中有1000万条数据,现在要查询第1000万条数据,这样就需要查找1000万次。顺序查找的问题是存储数据的结构是线性的,也就是说想要查询第2条数据,我需要知道第1条数据是什么。大家可能会考虑到为什么不要数组,直接通过下标就可以查到数据,问题是数组是固定长度的,那么数组长度定义为多少合适呢?


二叉树

要解决链表问题,我们可以使用树来解决,先来看下二叉树,二叉树与链表的区别是链表就一条搜索路径,二叉树有两条搜索路径。上表中对应的二叉树如下。
image.png
与链表一样,我们看看查询需要的次数,可以看出:二叉搜索树通过增加了一条搜索路径,提高了查询效率,查找的效率取决于树的深度(高度)

select * from user where password = 33 ; //查找1次
select * from user where password = 36 ; //查找2次
select * from user where password = 1 ; //查找2次
select * from user where password = 26 ; //查找3次
select * from user where password = 9 ; //查找4次
select * from user where password = 47 ; //查找3次
select * from user where password = 5 ; //查找5次
select * from user where password = 99 ; //查找4次
select * from user where password = 52 ; //查找5次

假设有1~9个数字要存储在二叉树中。由于二叉树的特点是,任一节点的左子节点都小于父节点,右子节点都大于父节点。当插入的数据是单向递增或递减时,二叉树就会变成线性结构,查询效率与链表就一致了
请添加图片描述


红黑树

image.png
请添加图片描述

为了解决二叉树变链表的问题,出现了红黑树。红黑树会做一个平衡。平衡后树的高度就变成了4层,相比于二叉树,效率更高。
但是红黑树依然存在瓶颈,以1000万条数据为例(树的高度大概是24),最坏的情况需要查找24次!查找24次是概念呢?,MySQL存储数据是存文件的,每次查找都需要读一次文件到内存,即最坏的查询情况需要读24次文件到内存,也就是24次IO操作,这是非常耗时的。


B树

image.png
因为树的高度问题,出现了B树(多路查找平衡树),B树的思路是多叉树。只要分叉越多,那么每一层可以存放的元素就越多,树的层级自然而然就会降低。那么分叉肯定是越多越好,最多可以多到什么程度呢?这取决于MySQL一页可存储的数据量是多少,我们可以通过SQL命令来查询:
image.png
可以看到MySQL默认一页是16384字节,大约是16kb。假设一条数据1KB为例,一颗高度为3的B树,可以存储的数据个数是 16 * 16 * 16 = 4096


B+树

image.png
我们知道Innodb的索引数据结构是B+树。可以我们看到B+树跟B树好像没有什么太大的差距,这里是数据量少的原因。B树效率高是因为分叉多,每行的节点变多了。通过计算高度为3的B树只能存放4096条数据,显然还待演进。

B树中的每个节点都包含指针和数据两部分,数据所占据的位置是较大的,我们是否可以考虑第一层第二层只存放指针,将数据都放到第三层。这样单个叶子节点可以存放的指针数量是 1170(16 * 1024 /(8+6))左右,那么高度为3的B+树就可以存储1170117016个数据(假设索引使用的是bigint类型(java中占8个字节),6是一个指针需要的空间)

细心的小伙伴会发现实际B&#43;树的叶子节点之间会有一个单向的箭头&#xff0c;实际这里的箭头是双向的&#xff0c;图不对。这个箭头代表的是前后叶子节点的存储位置。索引是排序后的数据结构&#xff08;大家可以观察第二行就是有序的&#xff09;。比如查询语句select * from table where num <5&#xff1b;查询时直接取5左边的数据即可&#xff0c;及大提高了效率。

可能有人会有这样的疑问&#xff1a;**为什么不把所有的索引放第一层&#xff0c;这样只要两层不就好了吗&#xff1f;**原因如下


  1. 如果第一层有几百M数据&#xff0c;一次磁盘io也加载不了那么多数据
  2. ram中使用是折半查找&#xff0c;几千万数据也不会很快

哈希

image.png
我们一般讲索引都是B&#43;树&#xff0c;实际MySQL中还有一种选择是hash。在查询时where后的条件是等值查询&#xff08;&#61;或in&#xff09;下&#xff0c;hash的速度比B&#43;树是要快的。B&#43;树有3层&#xff0c;哈希只需要计算一次即可。

既然哈希效率这么快&#xff0c;为什么没有人使用呢&#xff1f;主要有两个原因&#xff1a;


  1. 使用场景少&#xff0c;实际场景范围查询更多
  2. 哈希会有哈希冲突

最左前缀原理

最左前缀原理就是当表设置了组合索引&#xff0c;那么必须满足最左边字段在前的规定&#xff0c;下面通过实际案例来看。

可以这么思考&#xff1a;索引这么好&#xff0c;为什么不每个字段都加个索引&#xff1f;实际加索引就会生成一颗索引树。大量的索引树是非常占用存储空间的。所以实际场景中一般都是组合索引用的比较多一点。假设上表中创建了组合索引&#xff08;name,password&#xff09;。那么以下sql是否会走这个组合索引的SQL。

select * from user where name &#61; ? and password &#61; ? ;
select * from user where password &#61; ? and name &#61; ? ;
select * from user where name &#61; ? ;

  1. 第一个SQL会走索&#xff1a;左边是name&#xff0c;后面是password&#xff0c;完美用上
  2. 第二个SQL不走索引&#xff1a;虽然两个字段都有&#xff0c;但是name在后
  3. 第三SQL会走索引&#xff1a;组合索引只要满足组合中的第一个字段即可

聚集索引与非聚集索引

实际场景中&#xff0c;表中虽然有些业务字段可以当做主键&#xff0c;但是一般不这么做。一般都是添加一列作为数据的主键。这个主键也叫聚集索引&#xff0c;是用来生成主键树的。innodb下这个主树上存有数据的&#xff0c;回主树的过程就称为回表。

如果建表时没有设置主键&#xff0c;那么数据库就尝试查找某个字段没有重复的&#xff0c;用这个字段生成主树。如果没有这样的字段&#xff0c;数据库会为我们生成一个rowid&#xff08;隐藏列&#xff09;&#xff0c;使用隐藏列来生成主树。


学习时思考过的问题及答案


  1. 为什么线性结构不用二分查找&#xff1a;因为数据是读到内存里&#xff0c;数据量大的情况下内存放不放的下是个问题
  2. 红黑树&#xff0c;二叉平衡树层级高。两千万数据大概32层&#xff0c;32次IO是否能接收&#xff1a;磁盘IO是非常耗时的操作&#xff0c;是从文件中读数据到内存中
  3. B树采用空间换时间&#xff1a;将原来32层变成3层。增加了每次读到内存里数据的多少&#xff0c;减少了树的高度&#xff08;即减少了时间&#xff09;
  4. 辅助索引为什么不存所有数据&#xff1f;存两份一样的数据没有意义&#xff0c;还要考虑两棵树的数据同步问题
  5. 回表会增加几次IO&#xff1a;具体看主树的层级&#xff0c;如果三层就是3次

推荐阅读
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • 如何在php中将mysql查询结果赋值给变量
    本文介绍了在php中将mysql查询结果赋值给变量的方法,包括从mysql表中查询count(学号)并赋值给一个变量,以及如何将sql中查询单条结果赋值给php页面的一个变量。同时还讨论了php调用mysql查询结果到变量的方法,并提供了示例代码。 ... [详细]
  • C++语言入门:数组的基本知识和应用领域
    本文介绍了C++语言的基本知识和应用领域,包括C++语言与Python语言的区别、C++语言的结构化特点、关键字和控制语句的使用、运算符的种类和表达式的灵活性、各种数据类型的运算以及指针概念的引入。同时,还探讨了C++语言在代码效率方面的优势和与汇编语言的比较。对于想要学习C++语言的初学者来说,本文提供了一个简洁而全面的入门指南。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • 开发笔记:Docker 上安装启动 MySQL
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Docker上安装启动MySQL相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Java的公式汇总及相关知识,包括定义变量的语法格式、类型转换公式、三元表达式、定义新的实例的格式、引用类型的方法以及数组静态初始化等内容。希望对读者有一定的参考价值。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 本文介绍了Cocos2dx学习笔记中的更新函数scheduleUpdate、进度计时器CCProgressTo和滚动视图CCScrollView的用法。详细介绍了scheduleUpdate函数的作用和使用方法,以及schedule函数的区别。同时,还提供了相关的代码示例。 ... [详细]
  • 1网络设备驱动的结构Linux网络设备驱动程序体系结构如下图,从上到下依次划分为4层,依次为网路协议接口层、网络设备接口层,提供实际功能的设备驱动功能层以及网络设备与媒介层。 ... [详细]
author-avatar
荷-蘭水
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有