Mysql 学习(五)InnDB 存储引擎-B+树索引的使用

news2024/11/13 20:06:17

基础知识

  • 了解了表索引的底层是B+树结构,我们也要学会如何将这个结构的优势发挥出来,我们先来回顾上一节的重点,也就是总结一下B+树的特点
  • 索引对应的是一棵B+树,而B+树对应的很多层,每一层存储的数据对应的是下一层节点的位置,最底层存放的是用户数据记录
  • B+树中每层节点都是按照索引列值从小到大的顺序排序而组成了双向链表,而且每个页内的记录(不论是用户记录还是目录项记录)都是按照索引列的值从小到大的顺序而形成了一个单链表。如果是联合索引的话,则页面和记录先按照联合索引前面的列排序,如果该列值相同,再按照联合索引后边的列排序。
  • 通过索引查找记录是从B+树的根节点开始,一层一层向下搜索。由于每个页面都按照索引列的值建立了Page Directory(页目录),所以在这些页面中的查找非常快。

索引的优缺点

  • 一个东西的诞生肯定有好有坏,索引也有好处和坏处
  • 好处:查询更快
  • 坏处:
    • 需要更多的空间去存放,我们知道每一个索引就是一棵b+树,所以索引建的越多,空间占用就会越大
    • 对记录进行维护的性能消耗更大,因为我们是通过一个链表进行连接的,但你要对其中一个节点进行操作的时候,性能可想而知,并且你如果改变的是索引的相关数据,你还要将索引的B+树一起维护。

如何使用索引

  • 鉴于以上索引的缺点,我们需要合理的创建索引和使用索引,因为不规范的使用,会导致索引无法生效,下面根据常用的业务场景,我们从三个维度去讲解如何使用索引
  • 为了方便理解我们也建一个表
CREATE TABLE person(
    id INT NOT NULL auto_increment,
    name VARCHAR(100) NOT NULL,
    birthday DATE NOT NULL,
    phone_number CHAR(11) NOT NULL,
    country varchar(100) NOT NULL,
    PRIMARY KEY (id),
    KEY idx_name_birthday_phone_number (name, birthday, phone_number)
);

查询

  • 主键索引我们这边就画图了,重点是联合索引,联合索引比较复杂,我们这边画一个图
    在这里插入图片描述
  • 联合索引的排序上一节也讲了,是根据字段的顺序来排序的,举个例子,idx_name_birthday_phone_number 这个索引树,排序是这样的:
  • 先按照name排序
  • 在name值相等的情况下,再根据birthday排序
  • 在name和birthday值相等的情况下,再根据phone_number 排序

字符串排序

  • 数值类型的排序相比大家都清楚,但是字符串的排序是怎么样的呢?
  • 字符串的本质还是比较大小,不过他们是根据字母顺序的大小进行排序的,比如说 A 就比 B大,如果都是A 则比较第二个字母,比如 Aa 就比 Ab大,以此类推
  • 总结一下就是下面的过程:
    • 先比较字符串的第一个字符,第一个字符小的那个字符串就比较小
    • 如果两个字符串的第一个字符相同,那就再比较第二个字符,第二个字符比较小的那个字符串就比较小。
    • 如果两个字符串的第二个字符也相同,那就接着比较第三个字符,依此类推。

匹配列前缀

  • 知道字符串时如何排序,我们就可以顺便讲讲,匹配列前缀的问题
  • 假设我们在 person 表 根据name这个字段创建一个索引,则就会有一个以name排序好的B+树索引
  • 如果我们只进行对应查询,只需要做几次二次排序就能找到对应的数据,比如 SELECT * FROM person WHERE name = 'Ashburn',数据如下图
    在这里插入图片描述
  • 我们知道真实场景中字符串的查询更多的是模糊查询,比如,我只想找 name 字段开头是A的名字,这种sql语句就会是这样:SELECT * FROM person WHERE name LIKE 'A%';,这种情况下会不会查询?
  • 答案是会的,因为字符串的排序本身就是按照首字母比对以此下去的,所以你如果想找A 就比较每个叶子点的name首字母是否比A大或者小,就能找到
  • 那假设我不按首字母顺序查找,我按最后一个,会吗?就比如 我查最后一个字母是a的 SELECT * FROM person WHERE name LIKE '%A',或者中间字母有A的 SELECT * FROM person WHERE name LIKE '%A%'
  • 答案是 都不会,因为这样不符合字符串排序的方式,所以会全局查询

全值匹配

  • 什么是全值匹配,就是搜索条件跟索引列一致,比如:SELECT * FROM person WHERE name = 'Ashburn' AND birthday = '1990-09-27' AND phone_number = '15123983239';
  • 全职怎么查,流程是下面:
    • 先按照name的值查找对应的索引,定位到name列的值
    • name列相同的记录又是按照birthday值进行排序,所以我们可以快速定位到brthday的值
    • name 和 birthday都相同的值又会根据phone_number 进行排序,所以我们可以迅速定位到记录
  • 看索引的查询就是这么简单快捷,避免了全局查询,那这些条件打乱顺序,索引还会触发吗?
  • 答案是肯定的,为啥呢,因为底层有查询优化器在负重前行,会分析这些搜索条件并且按照可以使用的索引中列的顺序来决定先使用哪个搜索条件,后使用哪个搜索条件。

匹配左边的列

  • 既然把条件都写上可以触发索引查询,那我少一个呢?比如我就查SELECT * FROM person WHERE name = 'Ashburn' AND birthday = '1990-09-27'
  • 答案是会的,流程我们走一遍就知道了
    • 先按照name的值查找对应的索引,定位到name列的值
    • name列相同的记录又是按照birthday值进行排序,所以我们可以快速定位到brthday的值
  • 可以看出,我们就算不查phone_number ,也会触发查询
  • 那再少一个呢,比如SELECT * FROM person WHERE name = 'Ashburn'
  • 答案是 会的,流程我们再走一遍:
    • 先按照name的值查找对应的索引,定位到name列的值
  • 这两个例子可以看出,联合索引不需要全部都写上,也能触发索引查询
  • 那我们再做个实验,比如:SELECT * FROM person WHERE birthday = '1990-09-27';,会触发索引吗?
  • 答案是 不会,为什么?
  • 因为B+树的数据页和记录先是按照name列的值排序的,在name列的值相同的情况下才使用birthday列进行排序,也就是说name列的值不同的记录中birthday的值可能是无序的。而现在你跳过name列直接根据birthday的值去查找,这跟全表查没有什么区别
  • 由此我们看出,如果我们想要使用联合索引查询,我们搜索的条件就必须有联合索引中从最左边连续的列,比如 name,name+birthday,name+birthday+phone_number
  • 所以选择联合索引列需要慎重,不然的容易导致不能命中索引的情况,浪费性能

匹配范围值

  • 上面讲到了我们搜索的条件就必须有联合索引中从最左边连续的列,但其实这还有个限制,这个限制就来源于范围索引
  • 我们先来看看正常的范围搜索是咋样的,比如:SELECT * FROM person WHERE name > 'Asa' AND name < 'Barlow';
    • 通过索引找到name = ‘Asa’
    • 通过索引找到name = ‘Barlow’
    • 把他们两中间的所有索引值拿出来
    • 回表找出数据
  • 那再多一个搜索条件呢,比如:SELECT * FROM person WHERE name > 'Asa' AND name < 'Barlow' AND birthday > '1990-01-01';
    • 通过条件name > ‘Asa’ AND name < ‘Barlow’ 来对name进行范围,查找的结果可能有多条name值不同的记录,
    • 对这些name值不同的记录继续通过birthday > '1980-01-01’条件继续过滤。
  • 这样的话跟我们查询索引的流程不一样,因为只有name值相同的情况下才能用birthday列的值进行排序,而这个查询中通过name进行范围查找的记录中可能并不是按照birthday列进行排序的,所以在搜索条件中继续以birthday列进行查找时是用不到这个B+树索引的。
  • 虽然对多个列都进行范围查找时只能用到最左边那个索引列,但是如果左边的列是精确查找,则右边的列可以进行范围查找,比方:SELECT * FROM person WHERE name = 'Ashburn' AND birthday > '1980-01-01' AND birthday < '2000-12-31' AND phone_number > '15100000000';
    • name = ‘Ashburn’,对name列进行精确查找,当然可以使用B+树索引了。
    • 由于name值相同了,可以对birthday进行索引排序查找
    • 由于birthday用了范围查找,对应的birthday值不一样,所以phone_number的查询不能用索引查询,只能遍历上一步查询得到的记录
  • 也就是说,上一个查询是先通过name和birthday的索引找到对应的记录,再通过遍历这些记录找到对应的phone_number数据

排序

  • 排序的时候,索引也能派上用场,讲个例子:SELECT * FROM person ORDER BY name, birthday, phone_number LIMIT 10;
  • 这个排序的流程:
    • 先按照name排序
    • 如果name值相同再按照birthday排序
    • 如果name和birthday值都相同,再按照phone_number 排序
  • 这个流程是不是很熟悉,这就是B+树的创建流程,所以排序的时候按照索引字段的规则,则可以直接根据索引排好序返回
  • 并且联合索引的匹配左边的列的规则同样适用于排序,比如ORDER BY name、ORDER BY name, birthday,当name的值一样的时候 ORDER BY birthday, phone_number 也是能生效的
  • 那什么情况,不能触发索引进行排序
    • ASC、DESC混用:对于使用联合索引进行排序的场景,我们要求各个排序列的排序顺序是一致的,也就是要么各个列都是ASC规则排序,要么都是DESC规则排序。为什么不能混用呢?
      • 举个例子就明白了,假设 SELECT * FROM person_info ORDER BY name asc, birthday DESC LIMIT 10;
      • 先从索引的最左边确定name列最小的值,然后找到name列等于该值的所有记录,然后从name列等于该值的最右边的那条记录开始往左找10条记录
      • 如果name列等于最小的值的记录不足10条,再继续往右找name值第二小的记录,重复上面那个过程,直到找到10条记录为止。
      • 这样的操作太消耗性能了,不如直接记录都加载到内存中,再用一些排序算法,比如快速排序、归并排序、等等排序等等在内存中对这些记录进行排序
    • WHERE子句中出现非排序使用到的索引列:因为这样压根用不到B+树,所以就算排序字段是索引,也没用
    • 排序列包含非同一个索引的列:这个很简单,就是用不到B+索引树
    • 排序列使用了复杂的表达式:修饰过的列,就是不是原先的列了,所以用不到索引的B+树

分组

  • 通过查询,排序,我们其实已经知道一个道理,就是B+树其实已经按照顺序将索引字段分组了,把相同的值放到一起,所以我们进行分组计算的时候,分组字段是索引字段并且满足索引字段的要求,就能很快地找到对应分组并且计算。
  • 举个例子:SELECT name, birthday, phone_number, COUNT(*) FROM person GROUP BY name, birthday, phone_number
    • 这个查询做了三次的分组:
    • 第一次把记录按照name值进行分组,所有name值相同的记录划分为一组
    • 第二次把每个相同的name值下面的birthday进行分组
    • 第三次把每个相同的name和birthday值下面的phone_number进行分组
  • 熟悉吗,当然,这直接可以使用B+树做对应的操作

注意事项

  • 因为二级索引是基于主键来做的B+树,意思就是我们查询完二级索引还得回表,也就是真正存放数据的地方找数据,所以如果需要回表得记录越多,使用二级索引的性能就越低,所以查询字段能不用*号就不用。

如何挑选索引

  1. 只为出现在WHERE子句中的列、连接子句中的连接列,或者出现在ORDER BY或GROUP BY子句中的列创建索引
  2. 最好为那些列的基数大的列建立索引,为基数太小列的建立索引效果可能不好,因为如果列数据只有0和1,每次查找跟全表没啥区别,没必要
  3. 索引列的类型尽量小

总结

  • B+树索引在空间和时间上都有代价,所以创建索引要谨慎
  • B+树索引适用于下面这些情况:
    • 全值匹配
    • 匹配左边的列
    • 匹配范围值
    • 精确匹配某一列并范围匹配另外一列
    • 用于排序
    • 用于分组
  • 在使用索引时需要注意下面这些事项:
    • 只为用于搜索、排序或分组的列创建索引
    • 为列的基数大的列创建索引
    • 索引列的类型尽量小
    • 可以只对字符串值的前缀建立索引
    • 只有索引列在比较表达式中单独出现才可以适用索引
    • 为了尽可能少的让聚簇索引发生页面分裂和记录移位的情况,建议让主键拥有AUTO_INCREMENT属性。
    • 定位并删除表中的重复和冗余索引
    • 尽量使用覆盖索引进行查询,避免回表带来的性能损耗。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/440572.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用VS2022打包C#项目生成setup文件并部署

首先安装工具 新建Setup项目 先将\bin\Debug下的生成文件添加到里面 添加文件夹将我们需要的文件放入 添加项目输出 在用户桌面添加快捷方式 简单的安装 其实右键项目》生成&#xff0c;然后就在debug这个目录下 下一步下一步就可以了 安装好桌面就有了 添加卸载…

如何把较大的word文档压缩变小,3个高效处理法

在日常工作和学习中&#xff0c;我们经常使用word文档来创建和编辑文件。由于Word文档提供了创建专业和精美文档的便捷工具&#xff0c;并且能够节省用户大量的时间&#xff0c;因此是用户使用频率最高的文字处理程序之一。然而&#xff0c;一些较大的Word文档会占用大量存储空…

【JAVA程序设计】(C00129)基于Springboot+Vue前后端分离的在线考试管理系统

基于SpringbootVue前后端分离的在线考试管理系统 项目简介项目获取开发环境项目技术运行截图 项目简介 基于Springbootvue开发的前后端分离的学生考试系统为三个角色&#xff1a;系统管理员、教师、学生 管理员角色包含以下功能&#xff1a; 题库管理、试题管理、考试管理、阅…

太阳能电池IV测试软件的主要功能,太阳能电池特性测试

太阳能电池测试软件是一种专门用于测试太阳能电池的软件。太阳能电池是一种能够将太阳能转化为电能的装置&#xff0c;它的性能直接影响到太阳能电池发电系统的效率和稳定性。因此&#xff0c;太阳能电池测试软件的开发和使用对于太阳能电池行业的发展具有非常重要的意义。 一、…

[ARM+Linux] 基于全志h616外设开发笔记

修改用户密码 配置网络 nmcli dev wifi 命令扫描周围WIFI热点 nmcli dev wifi connect xxx password xxx 命令连接WiFi 查看ip地址的指令&#xff1a; ifconfig ip addr show wlan0 SSH登录 这是企业开发调试必用方式&#xff0c;比串口来说不用接线&#xff0c;前提是接入网络…

手把手带你了解《线程池》

文章目录 线程池的概念池的目的线程池的优势为什么从池子里拿线程更高效&#xff1f;构造方法参数讲解线程拒绝策略模拟实现线程池一个线程池设置多少线程合适&#xff1f; 线程池的概念 线程池&#xff1a;提前把线程准备好&#xff0c;创建线程不是直接从系统申请&#xff0…

【linux】——日志分析

文章目录 1. 日志文件1.1 日志文件的分类1.2 日志文件保存位置1.2.1 内核及系统日志1.2.2 日志消息的级别1.2.3 日志记录的一般格式1.2.4 用户日志分析 程序日志分析日志管理策略 远程收集日志 1. 日志文件 1.1 日志文件的分类 ● 日志文件是用于记录Linux系统中各种运行消息的…

Flutter PC桌面端 控制应用尺寸是否允许放大缩小

一、需求 桌面端中&#xff0c;登录、注册、找回密码页面不允许用户手动放大缩小&#xff0c;主页面允许 二、插件 window_manager 使用教程请参照这篇博客&#xff1a;Flutter桌面端开发——window_manager插件的使用 题外话&#xff1a; 之前使用的是bitsdojo_window插件…

202303-第四周-山川软件产品资讯

山川软件愿为您提供最优质的服务。 您的每一个疑问都会被认真对待&#xff0c;您的每一个建议都将都会仔细思考。 我们希望人人都能分析大数据&#xff0c;人人都能搭建应用。 因此我们将不断完善DEMO、文档、以及视频&#xff0c;期望能在最大程度上快速帮助用户快速解决问…

3、Typescript中补充的六个类型

1、元组 元组可以看做是数组的拓展&#xff0c;它表示已知元素数量和类型的数组。确切地说&#xff0c;是已知数组中每一个位置上的元素的 类型&#xff0c;来看例子&#xff1a; let tuple: [string, number, boolean]; tuple ["a", 2, false]; tuple [2, "…

FPGA | 延迟模型

实际逻辑元器件和它们之间的传输路径都会存在延迟。因此&#xff0c;必须检查设计中的延迟是否满足实际电路的时序约束要求。可以用时序仿真的方法来检查时序&#xff08;timing&#xff09;&#xff0c;即在仿真时向元件或路径中加入和实际相符的延迟信息&#xff0c;并进行相…

4.34、组播(多播)

4.34、多播 1.组播(多播)的介绍①组播地址②如何设置组播&#xff08;组播的使用&#xff09; 2.代码编写①服务端②客户端 1.组播(多播)的介绍 单播地址标识单个 IP 接口&#xff0c;广播地址标识某个子网的所有 IP 接口&#xff0c;多播地址标识一组 IP 接口。单播和广播是寻…

ThreadPoolExecutor原理剖析

1.前言 1.1 为什么要使用线程池&#xff1f; 线程池主要为了解决两个问题 一是当执行大量异步任务时&#xff0c;线程池能够提供较好的性能&#xff0c;避免了重复创建和销毁线程带来的开销二是线程池提供了一种资源限制和管理的手段&#xff0c;比如限制线程个数&#xff0…

【蓝桥杯】最难算法没有之一· 动态规划真的这么好理解?(引入)

欢迎回到&#xff1a;遇见蓝桥遇见你&#xff0c;不负代码不负卿&#xff01; 目录 一、何为动态规划DP 二、记忆化搜索 典例1.斐波那契数列 方法一&#xff1a;暴力递归 方法二&#xff1a;记忆化搜索 变形题 典例2&#xff1a;爬楼梯&#xff08;青蛙跳台阶&#xf…

海睿思分享 | 低而不LOW的低代码开发

低代码&#xff08;Low-Code&#xff09; 是指输出最少的代码&#xff0c;快速完成软件系统开发&#xff0c;进而实现降低开发成本的成效。 Low-Code 中的“Low”与网络热词“LOW”同音同字&#xff0c;前者通常理解为低成本或少量的代码输出&#xff0c;后者是对低认知已见问…

【数据结构】数据结构小试牛刀之单链表

【数据结构】数据结构小试牛刀之单链表 一、目标二、实现1、初始化工作2、单链表的尾插2.1、图解原理2.2、代码实现解答一个疑问 3、单链表的尾删3.1、图解原理3.2、代码实现 4、打印单链表5、单链表的头插5.1、图解原理5.2、代码实现 6、单链表的头删6.1、图解原理6.2、代码实…

【Linux系统】理解Linux中进程间通信

Linux进程间通信 1 进程间通信的介绍1.1为什么要有进程间通信1.2 为什么能进程间通信 2 进程间通信的框架2.1 进程间通信机制的结构2.2 进程间通信机制的类型2.2.1 共享内存式2.2.2 消息传递式 2.3 进程间通信的接口设计 3 进程间通信机制简介4 详细讲解进程间通信部分机制&…

【OAuth2.0 Client 总结】对接github第三方登录以及其他第三方登录总结

之前搞 oauth 登录一直没有搞好&#xff0c;客户端、授权服务端、资源端一起搞对于我刚接触的小菜鸡来说&#xff0c;难度有点大。 然后就先搞了个 Client 端对接 Github 登录。 网上关于 Github 登录的资料有很多&#xff0c;而且框架对 Github 集成的也很好&#xff0c;配置起…

【深入解析K8S专栏介绍】

序言 时间永远是旁观者&#xff0c;所有的过程和结果&#xff0c;都需要我们自己去承担。 Kubernetes (k8s) 是一个容器编排平台&#xff0c;允许在容器中运行应用程序和服务。 专栏介绍 欢迎订阅&#xff1a;【深入解析k8s】专栏 简单介绍一下这个专栏要做的事&#xff1a; 主…

8年测试老兵竟被面试官10分钟pass,这也太难了吧...

前言 随着软件测试领域对于技术要求越来越清晰&#xff0c;到现在测试人员在市场上的岗位需求也变得越来越复杂。极大部分的企业都开始对自动化测试岗位有了更多的需要。自然而然&#xff0c;面试就相对于非常重要了。 笔试部分 1.阐述软件生命周期都有哪些阶段&#xff1f;…