MySQL深度理解-深入理解MySQL索引底层数据结构与算法

1.引言

        在项目中会遇到各种各样的慢查询的问题,对于千万级的表,如果使用比较笨的查询方式,查询一条SQL可能需要几秒甚至几十秒,如果将索引设置的比较合理,可以将查询变得仍然非常快。

2.索引的本质

        索引:帮助MySQL高效获取数据的排好序的数据结构。

        索引的数据结构:

        1.二叉树

        2.红黑树

        3.Hash表

        4.B-Tree

        如果表中没有索引,检索数据的时候就需要一行一行的去找,消耗的时间非常大。

        每个数据在存储的时候,存储在磁盘上是,看起来是挨在一起的,但是其实不是挨在一起的,这个需要注意!!!

        从磁盘中读取一次数据,被称之为磁盘IO,一次磁盘IO的性能是比较低的,如果我们对磁盘上的某一个数据进行查询的时候,顺序从表中查询数据,每次拿一个进行比对一次,这样整体来看是比较消耗性能的,整体的性能是不高的。

        我们可以通过建立索引,将查询(磁盘IO)的次数控制在一定量内,就可以大大提升性能了。

        为什么不选择使用搜索二叉树?

        搜索二叉树并没有自平衡的功能,很有可能退化为链表结构。

        为什么不选择使用平衡搜索二叉树?如红黑树,AVL树?

        在平时生产环境使用数据库的时候,单表数据量常常会达到百万级,千万级,假设单表数据量为500万,即使使用红黑树,树的高度也会达到22,那么即使使用索引查询数据,也要可能进行22次磁盘IO操作,这个性能消耗对于MySQL来说也是比较大的。

        为什么B-Tree更加合适?

        1.B-Tree的特点:

        叶节点具有相同的深度,叶节点的指针为空。

        所有索引元素不重复。

        节点中的数据索引从左到右递增排列。

        2.B-Tree的数据结构:

        每个节点都会存储多个key-value结构,key存储的是索引值,data存储的是索引所在行数据的硬盘地址。

        为什么MySQL索引最终决定选择使用B+Tree(B-Tree的变种)?

        1.B+Tree的特点:

        非叶子节点不存粗data,只存储索引(做一个数据冗余),可以存放更多的索引数据。

        叶子节点包含所有的索引字段。

        叶子节点用指针连接,提高区间访问的性能。

        2.B+Tree的数据结构:

        MySQL中默认设定的一个节点可以存储的索引数据是16kb,可以使用SHOW GLOBAL STATUS like 'Innodb_page_size'查看“:

SHOW GLOBAL STATUS like 'Innodb_page_size';

        这是MySQL设置的默认值,不建议去修改,这是MySQL官方进行了大量测试得出的结果,多方考量了各种情况。

        现在举例展示定位数据的整体过程:

        整个查询30这个索引对应的数据的过程花费的消耗是三次磁盘IO和一次内存IO,相对来说内存的速度是极快的,对于磁盘IO消耗的时间是可以忽略不计的,所以这次查询消耗也就是三次网络IO。

        那为什么MySQL不直接干脆在一个节点上存储所有索引呢?这样只需要一次磁盘IO呀。

        答案:在系统中,内存是有限的,生产环境下一个表存储的数据可能会达到百万级,千万级,一次磁盘IO将所有的索引数据和数据查询到内存中,数据量相对来说是巨大的,直接将内存干爆了,设计的不合理。而且这样不就又变成了顺序查找数据了吗?这样整体查询数据的速度也不会很快的。

        MySQL选择16kb作为节点的存储空间,可以存储多少数据呢?

        答案:这里以索引是bigint类型的作例子,bigint = 8byte,数据地址在MySQL底层C语言中是占用了6byte,16kb / (8 + 6)byte,一个节点即可以存储1170个元素。假设data的数据是1kb,一个叶节点也是可以存储16个元素的。

        使用B+Tree对树高度进行削减的效果?

        假设表中有2000万的数据,使用索引建立的B+Tree的高度也只有3而已,也就是说最多三次磁盘IO就可以找到相对应的数据了。

        MySQL对于索引B+Tree节点常驻内存的操作。

        在MySQL低版本中,会将根节点常驻到内存中,在MySQL高版本中,会将所有的非叶子节点进行常驻内存,也就是说对于千万级数据量的表来说,查询数据的时间成本消耗只有查询叶节点中数据这一次的磁盘IO而已,可见查询速度是相当快的。

        面试题:对于B-Tree和B+Tree,为什么MySQL最终决定选择使用B+Tree?

        影响树查询的关键是什么?是树的高度,树越高需要进行的磁盘IO的次数就越多,2000万级别的数据表的索引使用B+Tree进行构建出来的树的高度仅有3,但是如果使用的是B-Tree,由于B-Tree的特性,构建起来整个树的高度绝对是大于3的,而且也不能做内存常驻等优化手段。

3.不同存储引擎索引实现

        首先先明确一个点:存储引擎的概念相对的是数据表,并不是相对的数据库,一个数据库中的多个数据表是可以分别使用不同的存储引擎的。

3.1MylSAM存储引擎

        MySQL数据库中的数据都是存储在磁盘中的,如果数据表采用的是MyISAM存储引擎,数据表在硬盘中对应存储的是以下三个文件:

        1.数据表名称.frm:数据表结构存储文件。

        2.数据表名称.MYD:数据表数据存储文件。

        3.数据表名称.MYI:数据表索引存储文件。

        即MyISAM存储引擎,数据存储和索引存储文件是分离存储的。

        在MYI索引文件中,存储的其实就是数据表构建的这一棵B+树,叶节点中的data数据是磁盘数据的地址,即当数据表采用的是MyISAM存储引擎的时候,通过索引去查询数据的时候,会根据B+Tree查询到数据的地址,然后再去MYD文件中查询真正的数据。

3.2InnoDB存储引擎

        如果数据表采用的InnoDB存储引擎,数据表在硬盘中对应存储的是以下两个文件:

        1.数据表名称.frm:数据表结构存储文件。

        2.数据表名称.ibd:数据表索引+数据存储文件。

        即InnoDB存储引擎,数据存储和索引存储文件是聚焦存储的。

        在InnoDB存储引擎中,通过索引字段查询到的Data是数据表中的数据,即数据表中的其他数据是和索引字段存储在一起的,即存储在B+Tree的叶节点中。

3.3聚集索引和非聚集索引

        非聚集索引:MyISAM存储引擎本身建立的索引就是非聚集索引,B+Tree叶节点的索引对应的Data是数据的地址,不是数据本身,即叶节点索引和数据是分开的,便是非聚集索引。

        聚集索引:InnoDB存储引擎本身建立的索引就是聚集索引,B+Tree叶节点的索引对应的Data是数据本身,即叶节点索引和数据是黏合在一起的,便是聚集索引。

        面试题:为什么建议InnoDB表必须建立主键,并且推荐使用整型的自增主键。

3.4面试题探究

3.4.1为什么InnoDB必须建立主键?

        之所以InnoDB必须建立主键,这与以InnoDB为存储引擎的数据表密切相关,InnoDB引擎的数据表中数据和索引是附着在一起的,所以数据表中一定必须要有一个索引,一般情况下,我们都是使用主键作为索引进行搭建B+Tree数据存储结构。

        如果发现数据表有主键,就会使用主键索引结合数据建立B+Tree结构存储相关数据。

        如果数据表没有主键,就会去数据表中从前向后去寻找一个所有数据都不重复的一列数据,借助该列数据作为索引去构建B+Tree。

        如果数据表没有主键,且没有从数据表中找到任何一列数据不一样,那么MySQL就会单独创建一个隐形的一列,去作为索引维护B+Tree。

        虽然MySQL最后还是可以帮助我们建立起主键索引,但是不建议将这种工作要求给MySQL去完成。

3.4.2为什么索引建议使用整形数据列

        前面提到过索引使用数据列中的数据建立的,并且每一层的整体元素都是以索引数据递增建立的索引节点,假设索引列中数据是整形,那么索引节点之间进行比较会非常简单,很容易就能比较成功,将整棵B+Tree建立起来。

        但是如果索引数据不是整形数据,而是字符串数据之类的,比较大小就会比较耗费性能,整形数字容易比较,性能较高,所以建议索引建立走整形数据列。

3.4.3为什么索引建立使用自增整形数据列

        前面已经介绍了为什么建议使用整形数据列,主要的目的就是为了提升索引树构建时的比较性能。

        其实索引元素选择为自增的元素也是为了提高索引树构建的整体性能。

        设定B+Tree中单节点设定为最多可以有四个元素,现在B+Tree中有七个元素,分别是1,2,3,4,5,6,8,那么建立起来的B+Tree就会如下所示:

        但是如果现在不遵循自增原则,现在插入一个0007元素进去,会将元素插入到0006和0007之间:

        但是现在最右侧的节点已经达到了最大元素数量,所以会进行分裂为两个节点,分裂为两个节点之后就会如下所示:

        可以发现除了进行节点分裂操作以外,还进行了自平衡操作,整个过程的时间成本损耗是比较大的,所以建议建立索引的时候是整形自增的。

4.聚簇索引和非聚簇索引

        在MySQL中索引被划分为两种,一种是聚簇索引另一种是非聚簇索引。

        其中聚簇索引的意思是索引叶节点对应的数据是原始数据(即真数据),非聚簇索引的意思是索引叶节点对应的数据是数据的地址。

        接下来就从存储引擎和索引类型两个方面去区分讲解。

4.1存储引擎

        MyISAM存储引擎使用的索引是非聚簇索引,即索引B+Tree的叶节点对应的数据是数据的地址,不是真实的数据。

        InnoDB存储引擎使用的一级索引(即主键索引)是聚簇索引,二级索引是非聚簇索引,下一步详细讲一下为什么要如此划分。

4.2索引类型

        这里主要是介绍InnoDB存储引擎的索引类型。

        索引类型分为主键索引和二级索引,主键索引顾名思义就是该字段既是主键又是索引,二级索引就是主键之外的字段建立的索引。

        主键索引的B+Tree索引树叶节点对应的数据是真实数据,所以主键索引被称之为聚簇索引。

        二级索引的B+Tree索引树叶节点对应的数据是数据的地址,所以二级索引被称之为非聚簇索引,二级索引之所以被设计为是非聚簇索引是因为为了达到节省空间的效果。

        二级索引都需要进行一次回表操作,通过回表操作查询真实的数据。

5.Hash索引

        InnoDB存储引擎的数据表,除了可以建立以B+Tree为存储数据结构的索引以外,还可以建立起以Hash为存储结构的索引。

        Hash索引不支持范围查找但是如果只是进行等值/IN查找速度会非常快,可以在项目中灵活使用(IO查询次数邵),因为对索引的key进行一次查询就可以定位到数据。

        Hash索引的索引对应的数据都是数据的地址,属于非聚簇索引。

6.联合索引(多字段索引)

6.1概念

        KEY idx_name_age_position (name age position) USING BTREE

        这条语句的意思是建立一个使用B+Tree结构构建的联合索引idx_name_age_position,该联合索引涉及到多个字段,分别是name,age,position字段。

        构建出的B+Tree如下:

        其中数据进行排序的时候,是先按照name排序,再按照age排序,再按照position进行排序。

        如果联合索引建立的是联合主键索引,叶节点对应的数据就是主键索引,通过联合索引查询到数据后,就会先获取到主键索引数据,再去回表查询主键索引对应的真实数据。

        如果是辅助索引,

6.2最左前缀原则

        最左前缀原则保证了,联合索引只在SELECT查询语句使用时,有最左字段时才会生效使用,否则索引不会生效。

        解释一下为什么不会生效,还是看刚刚建立的索引树:

        查询语句1:SELECT * FROM table WHERE name = Bill AND age = 30

        执行该SQL语句时,会先通过name定位到节点,然后查询到最左边的数据时,发现该数据是最后一个30,又由于数据组织是排好序的,所以就不会继续向下找了,该索引树起到了辅助快速查询的作用。

        查询语句2:SELECT * FROM table WHERE age = 30。

        如果是这样就无法进行正常定位了,因为无法age只能在确定的name里是小范围有序的,如果放到全局来看,age是乱序的,所以此时联合索引会失效,就不会走联合索引了。

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

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

相关文章

Django母婴商城项目实践(九)- 商品列表页模块

9、商品列表页模块 1、业务逻辑 商品模块分为:商品列表页 和 商品详情页 商品列表页将所有商品按照一定的规则排序展示,用于可以从销量、价格、上架时间和收藏数量设置商品的排序方式,并且在商品左侧设置分类列表,选择某一个分类可以筛选出对应的商品信息。 商品列表页…

8、STM32每个系列的区别

1、F1和F4的系列的区别 F1采用Crotex M3内核,F4采用Crotex M4内核。F4比F1的主频高。F4具有浮点数运算单元,F1没有浮点单元。F4的具备增强的DSP指令集。F407的执行16位DSP指令的时间只有F1的30%~70%。F4执行32位DSP指令的时间只有F1的25% ~ 60%。F1内部S…

DeepSPV:一种从2D超声图像中估算3D脾脏体积的深度学习流程|文献速递-医学影像算法文献分享

Title题目DeepSPV: A deep learning pipeline for 3D spleen volume estimation from 2Dultrasound imagesDeepSPV:一种从2D超声图像中估算3D脾脏体积的深度学习流程01文献速递介绍1.1 临床背景 脾肿大指脾脏增大,是多种潜在疾病的重要临床指标&#x…

病历数智化3分钟:AI重构医院数据价值链

一、方案概述本方案针对某省医联体医院病例数据管理需求,通过AI技术实现病历数字化→信息结构化→数据应用化的全流程改造。系统采用双端协同架构: - 普通用户端:为一线医护人员提供病历拍摄、AI识别修正、安全上传功能 - 管理员后台&#…

CSS+JavaScript 禁用浏览器复制功能的几种方法

🛡️ 禁用浏览器复制功能完整指南 网页中禁用用户的复制功能,包括 CSS 方法、JavaScript 方法、综合解决方案以及实际应用场景。适用于需要保护内容版权、防止恶意爬取或提升用户体验的场景。 📋 目录 🚀 快速开始&#x1f3a8…

Java 虚拟线程在高并发微服务中的实战经验分享

Java 虚拟线程在高并发微服务中的实战经验分享 虚拟线程(Virtual Threads)作为Java 19引入的预览特性,为我们在高并发微服务场景下提供了一种更轻量、易用的并发模型。本文结合真实生产环境,讲述在Spring Boot微服务中引入和使用虚…

《拆解WebRTC:NAT穿透的探测逻辑与中继方案》

WebRTC以其无需插件的便捷性,成为连接全球用户的隐形桥梁。但很少有人知晓,每一次流畅的视频对话背后,都藏着一场与网络边界的无声博弈——NAT,这个为缓解IPv4地址枯竭而生的技术,既是网络安全的屏障,也是端…

前端开发 React 组件优化

1. 使用 React.memo 进行组件优化问题:当父组件重新渲染时,子组件也会重新渲染,即使它的 props 没有变化。解决方案:使用 React.memo 包裹子组件,让其只在 props 变化时才重新渲染。示例场景:展示一个显示计…

变频器实习DAY12

目录变频器实习DAY12一、继续,柔性平台测试!上午 王工Modbus新功能测试下午 柔性平台继续按照说明书再测一遍附加的小知识点中国狸花猫.git文件附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^)变频器实习DAY12 一、继续,柔性平台测试&…

Redis--多路复用

🧩 一、什么是“客户端连接”?所谓 客户端连接 Redis,指的是:一个程序(客户端)通过网络连接到 Redis 服务端(比如 127.0.0.1:6379),建立一个 TCP 连接,双方可…

数组——初识数据结构

一维数组数组的创建数组是一种相同类型元素的集合数组的创建方式C99 中引入了变长数组的概念,变长数组支持数组的大小使用变量来指定明显这里的vs2019不支持变长数组数组初始化和不完全初始化第二个数组就是典型的不完全初始化,开辟了10个空间&#xff0…

技术速递|使用 Semantic Kernel 与 A2A 协议构建多智能体解决方案

作者:卢建晖 - 微软高级云技术布道师 翻译/排版:Alan Wang 在快速发展的 AI 应用开发领域,能够协调多个智能体已成为构建复杂企业级解决方案的关键。虽然单个 AI 智能体擅长特定任务,但复杂的业务场景往往需要跨平台、跨框架甚至跨…

前端跨域请求原理及实践

在前端开发中,"跨域"是一个绕不开的话题。当我们的页面尝试从一个域名请求另一个域名的资源时,浏览器往往会抛出类似Access to fetch at xxx from origin xxx has been blocked by CORS policy的错误。下面将深入探讨跨域请求的底层原理&#…

SpringBoot07-数据层的解决方案:SQL

一、内置数据源 1-1、【回顾】Druid数据源的配置 druid的两种导入格式 1-2、springboot提供的3种内置数据源的配置 若是不配置Druid, springboot提供了3中默认的数据源配置,它们分别是: 1. HikariCP(默认) 从 Spring…

前端自动化埋点:页面模块级行为跟踪与问题定位系统​​的技术设计方案

一、核心设计目标​​精细化监控​​:定位到页面中​​单个模块​​的曝光、点击等行为。​​低侵入性​​:业务代码与埋点逻辑解耦,降低开发维护成本。​​链路可追踪​​:串联用户从曝光到操作的完整行为路径。​​实时性​​&a…

Node.js 与 Java 性能对比

一、核心架构与任务模型对比Node.js 单线程事件循环 非阻塞I/O 通过V8引擎执行JavaScript,采用事件驱动模型,所有I/O操作(如网络请求、文件读写)均为非阻塞。单线程处理所有请求,但通过事件循环(Event Loo…

Python3常见接口函数

Python3常见接口函数一、基础内置函数 输入输出 print():输出内容input():读取用户输入 类型转换 int()、float()、str()、bool():基础类型转换list()、tuple()、set()、dict():容器类型转换bin()、hex()、oct():进制转…

《P4092 [HEOI2016/TJOI2016] 树》

题目描述在 2016 年,佳媛姐姐刚刚学习了树,非常开心。现在他想解决这样一个问题:给定一颗有根树,根为 1 ,有以下两种操作:标记操作:对某个结点打上标记。(在最开始,只有结…

TCP头部

TCP头部字段详解1. 源端口和目的端口(各16位)功能:标识发送和接收应用程序范围:0-65535(0-1023为知名端口)技术细节:客户端通常使用临时端口(1024-65535)服务端使用固定端…

LinkedList与链表(单向)(Java实现)

引入链表结构:在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后 搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入…