InnoDB存储引擎底层拆解:从页、事务到锁,如何撑起MySQL数据库高效运转(上)

目录

Page页**

B+树查询

如何加快记录的查询?

索引**

聚簇索引(主键)

二级索引(非主键)

联合索引——多列

bufferPool*

Free链表

flush链表

Flush链表刷新方式有如下两种:

LRU链表

针对LRU链表方案缺点的优化

redoLog*

redo简单日志类型

redo复杂日志类型(以MLOG_COMP_REC_INSERT为例:)

redo日志组

MTR(Mini-Transacti on)

redo日志缓冲区——log buffer

redo日志刷盘和日志文件组

undoLog*

事务**

MVCC

锁*


        你在电商平台点击“提交订单”按钮的瞬间,背后发生了什么?

        数据库需要在毫秒级内完成:查询商品库存、校验用户余额、生成订单记录、扣减库存、最终提交事务……这些操作看似“丝滑”,实则由MySQL的核心存储引擎——​​InnoDB​​默默支撑。

        但你可能从未想过:

  • 为什么InnoDB能在磁盘中快速找到一条记录?是靠怎样的“数据地图”?

  • 为什么它能同时处理上万并发事务,还能保证数据不丢、不乱?

  • 那些耳熟能详的“B+树索引”“Buffer Pool缓存”“redo log日志”……它们究竟如何协作?

        这篇文章,我们从InnoDB最底层的​​页(Page)​​开始,一步步拆解它的核心设计:B+树如何优化查询、Buffer Pool如何减少磁盘IO、redo/undo日志如何保障事务、事务与锁如何协同工作……最终还原这个“数据库隐形引擎”的底层逻辑。

Page**

        为了避免一条一条读取磁盘数据,InnoDB采取页的方式,作为磁盘和内存之间交互的基本单位。一个页的大小一般是16KB

        InnoDB为了不同的目的而设计了多种不同类型的页。比如:存放表空间头部信息的页、存放undo日志信息的页等等。我们把存放表中数据记录的页,称为索引页or数据页

往页中存储的数据 也称作:“记录”

记录的头信息包括以下:

记录是按照主键从小到大的顺序形成了一个单向链表。记录被删除后next_record会有影响查询也只能以头节点开始逐一向后查询,但是如果数据量很大,那么性能就无法保证了。针对这个问题,InnoDB采取了图书目录的解决方案,即:Page Directory

分组规则如下所示:

① 对于Infimum记录所在的分组只能有1条记录。② 对于Supremum记录所在的分组只能在1~8条记录之间。③ 剩下的其他记录所在的分组只能在4~8条记录之间。

分组步骤如下:① 初始情况下,一个数据页中只有Infimum记录和Supremum记录这两条,所以分为两个组。② 之后每当插入一条记录时,都会从页目录中找到对应记录的主键值比待插入记录的主键值大,并且差值最小的槽,然后把该槽对应的n_owned加1。③ 当一个组中的记录数等于8时,当再插入一条记录的时候,会将组中的记录拆分成两个组(一个组中4条记录,另一个组中5条记录)。并在拆分过程中,会在PageDirectory中新增一个槽,并记录这个新增分组中最大的那条记录的偏移量。

B+树查询

【B树&B+树的插入和删除图文详解】B树和B+树的插入、删除图文详解 - nullzx - 博客园

【与B树相同点】

一个节点可以存储多个元素。

与B树一样,叶子节点是有序的。

每个节点中的元素,也都按照从小到大的顺序排列,即:左小右大。

所有叶子节点都位于同一层,或者说根节点到每个叶子节点的高度都相同。

【与B树不同点】

B+树的叶子节点是有单向指针的,其中:MySQL中采用的是双向指针。

B+树的非叶子节点的元素是与叶子节点有冗余的。

如何加快记录的查询?

当记录越来越多,创建的页也会越来越多,如果仅通过链表方式遍历查询,性能会出现很大问题。如何解决呢?采用B+树结构,即:

叶子节点里存储完整的数据(数据页),非叶子节点存储主键索引(索引页)

索引**

聚簇索引(主键)

        聚簇索引的 B + 树叶子节点直接存储了完整的行数据。这意味着,InnoDB 表的数据实际上是按照聚簇索引的顺序进行物理存储的。

二级索引(非主键)

        二级索引的 B + 树叶子节点存储的是索引列的值以及对应的聚簇索引键值

当通过二级索引进行查询时,首先在二级索引的 B + 树中找到对应的叶子节点,获取到聚簇索引键值。然后,使用这个聚簇索引键值在聚簇索引的 B + 树中再次查找,才能获取到完整的行数据。这个过程称为 “回表”。例如,执行SELECT * FROM table WHERE email = 'example@mail.com',先在email的二级索引 B+树中找到对应的叶子节点,得到聚簇索引键(如id值),再通过这个id值在聚簇索引B+树中获取完整的行数据。

联合索引——多列

目录项记录的唯一性

        为了让新插入的记录能找到自己在哪个页中,就需要保证B+树同一层内节点的目录项记录除页号字段外是唯一的。所以二级索引的内节点的目录项记录的内容实际是有3部分构成的:索引列的值(c2)+主键值(c1)+页号(pageNo)。这样,如果c2列的值相同,那么可以接着比较主键值。所以,归其根源,我们可以认为,为c2列建立的二级索引其实相当于为(c2,c1)列建立了一个联合索引。

bufferPool*

为了缓存磁盘中的页,MySQL服务器启动时就向操作系统申请了一片连续的内存空间,他们给这片内存起名为Buffer Pool(缓冲池)。默认Buffer Pool只有128M,可以在启动服务器的时候配置innodb_buffer_pool_size(单位为字节)启动项来设置自定义缓冲池大小。Buffer Pool对应的一片连续的内存被划分为若干个页面,默认也是16KB,该页面称为缓冲页。为了更好的管理Buffer Pool中的这些缓冲页,InnoDB为每个缓冲页都创建了控制块,它与缓冲页是一一对应的。

Free链表

Buffer Pool的初始化过程中,是先向操作系统申请连续的内存空间,然后把它划分成若干个【控制块&缓冲页】对儿。当插入数据的时候,为了能够知道哪些缓冲页是空闲且可分配的,MySQL把所有空闲的缓冲页对应的控制块作为一个节点放到一个链表中,这个链表便称之为free链表。

flush链表

如果我们修改了Buffer Pool中某个缓冲页的数据,那么它就与磁盘上的页不一致了,这样的缓冲页也被称之为脏页(dirty page)。为了性能问题,我们每次修改缓冲页后,并不着急立刻把修改刷新到磁盘上,而是将被修改过的缓冲页对应的控制块作为节点加入到这个链表中,该链表也被称为flush链表。

Flush链表刷新方式有如下两种:

【1】从flush链表中刷新一部分页面到磁盘

后台线程也会定时从flush链表中刷新一部分页面到磁盘,刷新的速率取决于当时系统是否繁忙。——即:BUF_FLUSH_LIST

有时后台线程刷新脏页的进度比较慢,导致用户准备加载一个磁盘页到Buffer Pool中时没有可用的缓冲页。此时,就会尝试查看LRU链表尾部,看是否存在可以直接释放掉的未修改缓冲页。如果没有,则不得不将LRU链表尾部的一个脏页同步刷新到磁盘(与磁盘交互是很慢的,这会降低处理用户请求的速度)。——即:BUF_FLUSH_SINGLE_PAGE

【2】从LRU链表的冷数据中刷新一部分页面到磁盘

后台线程会定时从LRU链表的尾部开始扫描一些页面,扫描的页面数量可以通过系统变量innodb_lru_scan_depth来指定,如果在LRU链表中发现脏页,则把它们刷新到磁盘。——即:BUF_FLUSH_LRU

控制块里会存储该缓冲页是否被修改的信息,所以在扫描LRU链表时,可以很轻松地获取到某个缓冲页是否是脏页的信息。

LRU链表

线性预读:如果顺序访问某个区(extent,一个区默认64个页)的页面超过了innodb_read_ahead_threshold(默认56)的值,就会触发一次异步读取下一个区中全部的页到Buffer Pool中的请求。

随机预读:如果开启了随机预读功能(默认:innodb_random_read_ahead=OFF),如果某个区(extent)有13个连续的页面都已经被加载到了Buffer Pool中,无论这些页面是不是顺序读取的,都会触发一次异步读取本区全部的页到Buffer Pool中的请求。

针对LRU链表方案缺点的优化

  • 针对预读的优化①InnoDB规定,当磁盘上的某个页在初次加载(只是加载,没有涉及读取)到BufferPool中的某个缓冲页时,该缓冲页对应的控制块会被放到old区域的头部。这样预读页就只会在old区域,不会影响young区域中使用比较频繁的缓冲页。
  • 针对全表扫描的优化①虽然首次加载放到的是old区域的头部,但是由于是全表扫描,会对加载的数据进行访问,那么第一次访问的时候,就会将该页放到young区域的头部。这样仍然会把那些使用频率比较高的页面给“排挤”下去。②那怎么办呢?由于全表扫描有一个特点,就是它对某个页的频繁访问且总耗时很短。所以,针对这种情况,InnoDB规定,在对某个处于old区域的缓冲页进行第一次访问时,就在它对应的控制块中记录下这个访问时间,如果后续的访问时间与第一次访问的时间在某个时间间隔内(即:innodb_old_blocks_time,默认为1000,单位为ms),那么该页面就不会从old区域移动到young区域的头部,否则将它移动到young区域的头部。

chunk和BufferPool实例

redoLog*

什么是redo日志

如果我们只在内存的BufferPool中修改了页面,假设在事务提交后突然发生了某个故障,导致内存中的数据都失效了,那么这个已经提交的事务在数据库中所做的更改也就丢失了。针对这种问题,怎么处理呢?

【方案1】在事务提交时,把该事务修改的所有页面都刷新到磁盘,刷新成功了才提示事务提交成功刷新一个完整的数据页太浪费了虽然我们只修改了一条记录,但是会将这条记录所在的页(16KB)都刷新到磁盘上,会造成大量磁盘I/O的浪费。②随机I/O刷新起来比较慢一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,并且该事务修改的这些页面可能并不相邻。这就意味着将某个事务修改的BufferPool中的页面刷新到磁盘时,需要进行很多的随机I/O。而随机I/O要比顺序I/O慢,尤其是机械硬盘。

【方案2】在事务提交时,只需要把修改的内容记录一下就好了例如:“将第0号表空间第100号页面中偏移量为1000处的值更新为2。”

redo简单日志类型

在对页面的修改是极其简单的情况下,redo日志中只需要记录一下在某个页面的某个偏移量处修改了几个字节的值、具体修改后的内容是什么就好了,比如操作MaxRowId

MLOG_1BYTE:表示在页面的某个偏移量处写入1字节的redo日志类型。

MLOG_2BYTE:表示在页面的某个偏移量处写入2字节的redo日志类型。

MLOG_8BYTE:表示在页面的某个偏移量处写入8字节的redo日志类型。

MLOG_WRITE_STRING:表示在页面的某个偏移量处写入一个字节序列。

redo复杂日志类型(以MLOG_COMP_REC_INSERT例:

redo日志组

在执行语句的过程中产生的redo日志,被InnoDB划分成了若干个不可分割的组。比如:更新MaxRowID属性时产生的redo日志为一组,是不可分割的;向聚簇索引/二级索引对应B+树的页面中插入一条记录时产生的redo日志为一组,是不可分割的等等。InnoDB认为,比如向某个索引对应的B+树中插入一条记录的过程必须是原子的,不能说插入了一半之后就停止了。否则就会形成一棵不正确的B+树。所以他们规定在执行这些需要保证原子性的操作时,必须以组的形式来记录redo日志。在进行恢复时,针对某个组中的redo日志,要么把全部的日志都恢复,要么一条也不恢复。

MTR(Mini-Transacti on)

对底层页面进行一次原子访问的过程被称为一个Mini-Transaction(MTR)。

事务、语句、MTR、redo日志之间的关系,如下所示:① 1个事务可以包含N条SQL语句② 1条SQL语句可以包含N个MTR③ 1条MTR可以包含N条redo日志

redolog block

为了更好地管理redo日志,InnoDB把通过MTR生成的redo日志都放在了大小为512字节的页中,把用来存储redo日志的页称为block。

与Buffer Pool类似,写入redo日志时也不能直接写到磁盘中,实际上在服务器启动时就向操作系统申请了一大片称为redo log buffer(redo日志缓冲区)的连续内存空间,也可以将其简称为log buffer。这片内存空间被划分成若干个连续的redo log block。

其中,用innodb_log_buffer_size指定log buffer的大小。该启动选项的默认值为16MB。

redo日志缓冲区——log buffer

向log buffer中写入redo日志的过程是顺序写入的。其中,buf_free是一个全局变量,该变量指明后续写入的redo日志应该写到log buffer中的哪个位置。

一个MTR执行过程中可能产生若干条redo日志,这些redo日志是一个不可分割的组,所以并不是每生成一条redo日志就将其插入到log buffer中,而是将每个MTR运行过程中产生的日志先暂时存到一个地方,当该MTR结束的时候,再将过程中产生的一组redo日志全部复制到log buffer中

不同的事务是可能并发执行的,所以T1、T2的MTR可能是交替执行的。

redo日志刷盘和日志文件组

MTR运行过程中产生的一组redo日志在MTR结束时会被复制到log buffer中。可是这些日志总在内存里也不是办法,在一些情况下它们会被刷新到磁盘中:

**重点面试题:redolog 在哪些情况刷盘?

①log buffer空间不足50%的时候②事务提交的时候③ 后台有线程,大约以每秒1次的频率将log buffer中的redo日志刷新到磁盘。④ 正常关闭服务器时⑤ 做checkpoint时

在MySQL的数据目录中,默认有名称为:ib_logfile0和ib_logfile1的两个文件,logbuffer中的日志在默认情况下就是刷新到这两个磁盘文件中,也可以通过下一页中的配置参数对其进行调节和修改:

查看redo日志相关配置信息

datedir:查看数据目录所在位置。

innodb_log_group_home_dir指定了redo日志文件所在目录,默认值为当前的数据目录。

redo日志文件格式

lsn(logsequence number)

lsn是一个全局变量,用来记录当前总共已经写入的redo日志量。

lsn初始值为8704,也就是说,一条redo日志什么也没写入的时候,lsn的值就是8704。

redo日志刷新到磁盘

flush链表

刷入磁盘

mtr1和mtr2生成的redo日志虽然已经写到磁盘上的log file中了,但是它们修改的脏页仍然留在Buffer Pool中,所以它们的redo日志不可以被覆盖。随着系统运行,如果页a从Buffer Pool中刷到了磁盘上,那么页a对应的控制块就会从flush链表中移除掉。而且,它的redo日志占用的空间就可以被覆盖掉了。

chec kpoint

InnoDB通过全局变量checkpoint_lsn,来表示当前系统中可以被覆盖的redo日志总量是多少。这个变量的初始值也是8704(因为lsn的初始值就是8704)。比如,现在页a被刷新到了磁盘上,mtr1生成的redo日志就可以被覆盖了,所以进行一个增加checkpoint_lsn的操作。我们把这个过程称为执行一次checkpoint。

redo日志文件组中各个lsn值的关系,如下图所示:

innodb_flush_log_at_trx_commit

为了保证事务的持久性,用户线程在事务提交时,需要将该事务执行过程中产生的所有redo日志都刷新到磁盘中。

这个规则我们可以通过系统变量innodb_flush_log_at_trx_commit来进行配置修改,该变量有如下3个可选值:0:在事务提交时,不立即向磁盘同步redo日志,这个任务交给后台线程来处理;1:在事务提交时,需要将redo日志同步到磁盘。(默认值)2:在事务提交时,需要将redo日志写到操作系统的缓冲区中,但并不需要保证将日志真正刷新到磁盘。如果操作系统挂掉了,则数据丢失。

剩下内容在下篇持续更新,下方链接

InnoDB存储引擎底层拆解:从页、事务到锁,如何撑起MySQL数据库高效运转(下)-CSDN博客

undoLog*

事务**

MVCC

*

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

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

相关文章

【人工智能99问】GPT4的原理是什么?(32/99)

【人工智能99问】GPT4的原理是什么?(32/99) 文章目录GPT-4简介一、结构与原理二、使用场景三、优缺点分析四、训练技巧与关键改进五、示例说明六、总结GPT-4简介 一、结构与原理 1. 架构设计 GPT-4基于Transformer解码器架构,核心改进包括:…

//Q是一个队列,S是一个空栈,实现将队列中的元素逆置的算法。

#include<stdio.h> #include<stdbool.h> #include<stdlib.h> #define Size 6 typedef struct SqNode//队列结点 {int data;struct SqNode* next; }LinkNode; typedef struct SqQueue//队列 {LinkNode* front, * rear; //头指针和尾指针 }LinkQueue; typedef …

毕马威 —— 公众对人工智能的信任、态度及使用情况调查

文章目录 前言 一、背景介绍 二、对人工智能的信任与接受度 三、人工智能的使用与认知情况 四、人工智能的益处与风险 五、人工智能的监管与治理 六、工作场所的人工智能应用 七、人工智能对工作的影响 八、学生对人工智能的应用 九、核心启示 1.新兴经济体在公众与员工人工智能…

基于Spring Session + Redis + JWT的单点登录实现

实现思路 用户访问受保护资源时&#xff0c;若未认证则重定向到认证中心认证中心验证用户身份&#xff0c;生成JWT令牌并存储到Redis认证中心重定向回原应用并携带令牌应用验证JWT有效性并从Redis获取会话信息用户在其他应用访问时&#xff0c;通过相同机制实现单点登录 代码…

微服务Eureka组件的介绍、安装、使用

微服务 Eureka 组件的介绍、安装与使用详解 在微服务架构中&#xff0c;服务注册与发现是至关重要的一环&#xff0c;而 Eureka 作为 Netflix 开源的服务注册中心&#xff0c;广泛应用于 Spring Cloud 微服务体系中。本文将带你全面了解 Eureka 的概念、安装及在 Spring Boot …

【PostgreSQL内核学习:通过 ExprState 提升哈希聚合与子计划执行效率(一)】

PostgreSQL内核学习&#xff1a;通过 ExprState 提升哈希聚合与子计划执行效率&#xff08;一&#xff09;引言背景补丁的意义补丁概述JIT & LLVM实际例子&#xff08;以 PostgreSQL 为例&#xff09;提交信息提交描述引入 ExprState 进行哈希计算&#xff1a;支持 JIT 编译…

web端播放flv视频流demo(flv.js的使用)

需求&#xff1a;原本是需要前端播放RTMP视频流的&#xff0c;但是现在的浏览器都不支持flash插件了&#xff0c;让用户安装flash插件也不现实&#xff0c;所以直接让后端将RTMP视频流转换成flv视频流给到前端进行播放。 直接上demo&#xff0c;直接就能播放&#xff0c;如果遇…

【拍摄学习记录】04-拍摄模式/曝光组合

曝光组合全自动半自动光圈优先手动挡【固定物体长时间不变时候、闪光灯时候、】手机上的光学变焦与数码变焦是不同的&#xff0c;使用档位推荐可以提升画质。手机夜景模式长曝光【车流轨迹、星轨】HDR 大光比【日落时候使用】专业模式&#xff0c;【感光度iso、快门可以调节】…

新liunx部署mysql过程问题

首先看下是什么发行版 cat /etc/os-release CentOS Linux 7 ################################## 使用 yum下载包 发现不行 源不行 那就换成阿里的 # 进入 yum 源配置目录 cd /etc/yum.repos.d/ # 备份所有默认 repo 文件&#xff08;以 CentOS 为例&#xff0c;其他系统…

Python 第三方库:Beautiful Soup(HTML/XML 解释提取)

Beautiful Soup 是一个 用于从 HTML 和 XML 文件中提取数据的 Python 第三方库。它为复杂的网页结构提供了简单易用的解析接口&#xff0c;尤其适合网页爬虫和数据提取任务。Beautiful Soup 提供树型结构访问、标签搜索、属性提取等功能&#xff0c;并支持多种解析器&#xff0…

使用STM32CubeMX使用CAN驱动无刷电机DJI3508

简介 文章为笔记性质 硬件包括 大疆C板 电机调速器C620 DJI3508电机 CAN知识介绍 CAN的概念 CAN是控制器区域网络&#xff08;Controller Area Network&#xff09;的缩写。CAN总线是一种适用于工业设备的高性能总线网络。说白了就是也就是一种通讯方式而已。 把多个设…

Wi-Fi 802.11s自组网/EasyMesh自组网/802.11ah物联网

一、前期调研结论 前面详细探讨了自组网和5G无线通信网络、WiFi无线通信网络的差异&#xff1a; 自组网 v.s 5G v.s WiFi-CSDN博客 从“分级道路”角度理解无线通信网络拓扑包括从当前工业应用场景具体案例了解终端无线通信网络&#xff1a; 5G无线通信网络场景&#xff08;…

【基于hyperledger fabric的教育证书管理系统】

教育证书管理系统 系统概述 项目背景 随着数字化转型的深入推进&#xff0c;教育证书作为个人学术成就和专业资质的重要凭证&#xff0c;在就业市场、高等教育和职业发展中扮演着关键角色。然而&#xff0c;传统教育证书管理体系面临着数据孤岛、证书伪造、验证流程繁琐以及跨机…

【Flask】测试平台开发,集成禅道

概述&#xff1a; 由于公司多数测试人员还是在使用禅道&#xff0c;为了方便&#xff0c;就将禅道直接集成在我们的测试平台中 一般可以有几种实现方法 调用禅道的API集成集成本地部署的禅道-可能有跨域问题&#xff0c;需要解决 由于我这里已经部署了一台本地的禅道系统&…

《UE5_C++多人TPS完整教程》学习笔记45 ——《P46 待机与跳跃(Idle And Jumps)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P46 待机与跳跃&#xff08;Idle And Jumps&#xff09;》 的学习笔记&#xff0c;该系列教学视频为计算机工程师、程序员、游戏开发者、作家&#xff08;Engineer, Programmer, Game Developer, Author&#xff09; St…

用html+js下拉菜单的demo,当鼠标点击后展开,鼠标点击别的地方后折叠

使用html js实现下拉菜单demo&#xff0c;因为copy的网站菜单功能失效&#xff0c;就需要自己写一个逻辑&#xff0c;点击其他区域折叠菜单&#xff0c;可以参考&#xff1a;<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF…

OpenCV 核心技术:颜色检测与几何变换实战

在计算机视觉任务中&#xff0c;颜色空间转换和图像几何变换是两大基础且高频的操作 —— 前者用于精准分割特定颜色目标&#xff08;如交通信号灯、物体追踪&#xff09;&#xff0c;后者用于调整图像的尺寸、位置和视角&#xff08;如文档矫正、图像拼接&#xff09;。本文将…

[HFCTF2020]EasyLogin

文章目录TRYWP总结TRY 注册admin报错username wrong。 随便注册一个用户点击GetFlag&#xff0c;permission deny。 猜测可能是需要admin权限。 看cookie发现有&#xff1a; sses:aok&#xff1a;eyJ1c2VybmFtZSI6ImEiLCJfZXhwaXJlIjoxNzU2NDU1NjczMTAxLCJfbWF4QWdlIjo4NjQwM…

Java接口和抽象类的区别,并举例说明

Java接口和抽象类是面向对象编程中实现抽象的两种机制&#xff0c;它们在语法、设计目的和使用场景上有显著区别&#xff1a;一、核心区别‌定义方式‌抽象类&#xff1a;使用abstract class声明&#xff0c;可包含抽象方法和具体方法45。接口&#xff1a;使用interface声明&am…

docker-相关笔记

1: 导入镜像 docker load -i myimage.tar# 导出镜像 docker save myimage:latest > myimage.tar # 导入镜像 docker load -i myimage.tardocker load -i <文件> 功能&#xff1a;用于导入通过 docker save 命令导出的镜像归档文件&#xff08;通常是 .tar 格式&#…