InnoDB数据页

导读:

  我们已经知道了页是数据库存储的基本单位,知道了一条行记录的存储格式是怎样的,当数据越来越多时,那一条条行记录具体又是怎么在页中被组织起来的呢?

一、InnoDB数据页结构

二、总结

1、一条条行数据是如何在数据页中被组织起来的?

  首先,在每一个数据页中存在两个虚拟记录,一个代表最小记录,一个代表最大记录,它们被存储在数据页的 Infimum + Supremum 部分。

  紧接着,当向表中插入一些数据时,这些记录会被存储在数据页中的 User Records 中,这里不仅存储了其真实内容,还有存储了一些额外的数据,比如头信息中的 next_record,通过这个标记加上两个虚拟记录,使得记录形成了一条按照主键值由小到大的顺序的单链表(非插入顺序)。

  然后,为了数据能够查的更快,将数据页中的数据进行了分组,每组最后一个记录的地址偏移量作为槽,存放在 Page Directory(页目录),这样对于之后通过主键查询记录时,只需通过二分法确定目标结果存储在哪个组,然后在组中通过记录的 next_record 属性进行遍历即可找到目标。这样就完成了一个数据页中数据的组织。

  最后,一个数据页默认大小为 16 KB,当一个数据页不足存储新数据时,将会申请新的数据页进行存储。此时,数据页结构中的 File Header 就发挥作用,其包含 FIL_PAGE_PREV 和 FIL_PAGE_NEXT 两个属性,它们分别代表本页的上一个和下一个页的页号,这样就可通过这两个属性将许许多多的页就都串联起来了,形成一个双向链表。

  总的来说,各个数据页可以组成一个双向链表,而每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表,每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。

三、在 MySQL InnoDB 的页目录(Page Directory)设计中,为什么槽(Slot)记录的是分组中​​最大主键值​​,而不是最小值?

  在 InnoDB 数据页中,所有记录按主键顺序物理存储(升序排列)。页目录的槽将这些记录分成多个分组(例如,每个分组通常包含 4-8 条记录),槽的信息允许二分查找快速定位目标记录所在的分组,然后在该分组内进行线性查找(因为分组内记录数量较少,线性查找代价低)。使用最大主键值作为槽值的核心原因是:它使二分查找的决策更简单、更高效,减少了比较操作的歧义,并能直接利用主键单调递增的特性

1、为什么最大值优于最小值?

1.1、使用最大值时的二分查找逻辑​​:当目标主键(Target Key)与槽值比较时:

  • 如果目标主键 <= 槽值(分组最大主键),则目标记录可能位于该槽对应的分组或更早的分组(因为槽值是该分组的最大主键,目标值不超过最大值说明它可能在这个分组内)。
  • 如果目标主键 > 槽值,则目标记录一定位于后续的分组(因为分组最大主键小于目标值,说明目标不在当前或之前的任何分组)。

  这允许二分查找高效调整搜索范围(例如,调整 high 或 low 指针),每次比较都能明确缩小搜索范围,没有歧义。

1.2、使用最小值时的二分查找问题​​:如果槽记录分组的最小主键值:

  • 如果目标主键 < 槽值(分组最小主键),则目标记录一定位于更早的分组。
  • 如果目标主键 >= 槽值,则目标记录可能位于该槽对应的分组或更晚的分组。

  这种方式在比较时存在歧义:当目标主键 >= 槽值时,无法立即确定目标是否在分组内,因为分组最小主键只表示分组的起始点,但分组可能包含更大的值(例如,目标值可能落在两个分组之间)。这会导致二分查找需要额外的检查步骤(如检查后一个槽的最小值),或多次线性探测,降低了效率。

简单来说,使用最大值允许二分查找直接通过 target <= slot_value 条件锁定目标分组,而最小值会导致条件模糊,查询路径更长。

2、结合二分法的详细示例

下面通过一个具体例子说明。假设一个 InnoDB 数据页包含以下主键值(已排序):1, 3, 5, 7, 9, 11, 13, 15。为简化,假设每个分组包含2条记录,页目录有4个槽:

分组:组1 (1, 3)、组2 (5, 7)、组3 (9, 11)、组4 (13, 15)。

现在,执行二分查找目标主键为10的记录(10不存在,但查找过程相同)。

二分查找的伪代码如下(以方案A和B分别演示)

# 通用二分查找框架(查找可能的分组索引)

low = 0

high = num_slots - 1

while low <= high:

    mid = (low + high) // 2

    if target <= slots[mid].value:  # 比较目标值和槽值

        high = mid - 1  # 目标在左半部分(包括当前槽)

    else:

        low = mid + 1   # 目标在右半部分

# 结束时,low 是可能的目标分组索引

方案A:使用最大值的二分查找(高效)

  • 槽值设置:槽0 = 3(组1最大主键)、槽1 = 7(组2最大主键)、槽2 = 11(组3最大主键)、槽3 = 15(组4最大主键)。
  • 槽值数组:[3, 7, 11, 15](槽索引:0,1,2,3)
  • 查找目标:10
  • 二分查找过程:
    • 初始:low = 0, high = 3
      • mid = (0 + 3) // 2 = 1, 槽值 = 7
      • 比较:10 <= 7? False → 目标 > 7,所以目标在槽1之后,设置 low = mid + 1 = 2
    • 更新:low = 2, high = 3
      • mid = (2 + 3) // 2 = 2, 槽值 = 11
      • 比较:10 <= 11? True → 目标可能位于槽2或更早分组,设置 high = mid - 1 = 1
    • 现在 low = 2, high = 1,low > high,循环结束。返回候选索引 low = 2(可能的目标分组)。
  • 结果:槽2被选中(对应组3)。然后在组3内线性查找记录(9和11),发现10不存在。
  • 分析​​:仅2次比较就锁定了槽2(组3),因为最大值为11提供了明确的上界:10 <= 11表示目标一定在槽2之前的某个分组(或槽2本身),而前一步的 low 调整确保了高效收敛。整个查找过程直接,没有歧义。

方案B:使用最小值的二分查找(低效)

  • 槽值设置:槽0 = 1(组1最小主键)、槽1 = 5(组2最小主键)、槽2 = 9(组3最小主键)、槽3 = 13(组4最小主键)。
  • 槽值数组:[1, 5, 9, 13](槽索引:0,1,2,3)
  • 查找目标:10
  • 二分查找过程:
    • 初始:low = 0, high = 3
      • mid = (0 + 3) // 2 = 1, 槽值 = 5
      • 比较:10 <= 5? False → 目标 > 5,但无法确定是否在槽1分组内(因为 min=5,目标可能位于槽1或后续分组),设置 low = mid + 1 = 2
    • 更新:low = 2, high = 3
      • mid = (2 + 3) // 2 = 2, 槽值 = 9
      • 比较:10 <= 9? False → 目标 > 9,同样无法确定,设置 low = mid + 1 = 3
    • 更新:low = 3, high = 3
      • mid = (3 + 3) // 2 = 3, 槽值 = 13
      • 比较:10 <= 13? True → 目标可能位于槽3或更早分组,设置 high = mid - 1 = 2
    • 现在 low = 3, high = 2,low > high,循环结束。返回候选索引 low = 3(可能的目标分组)。
  • 结果:槽3被选中(对应组4)。但组4的最小主键是13 > 10,所以目标不在该分组。需要额外检查前一个槽(槽2,组3)或直接线性扫描组3,才能确定10在组3范围内(但不存在)。
  • ​​分析​​:使用了3次比较,但结束时 low=3 是错误的(组4最小主键13 > 10,目标不可能在组4)。二分查找后,必须额外检查槽2的槽值(9),才能发现目标可能位于槽2分组(因为 9 <= 10 但 10 < 13)。整个路径更长,存在歧义:当目标 > 槽值时,无法排除当前分组的可能性,导致 low 和 high 调整不精确。这会增加不必要的比较和分组访问次数。

关键区别总结

  • 决策清晰性​​:使用最大值时,槽值(最大主键)定义了分组的明确上界。二分查找中,target <= slot_value 直接表示目标位于索引 <= mid 的某个分组,从而高效缩小搜索范围。最小值则只能定义分组的起始点,没有上界信息,导致决策模糊。
  • ​​效率​​:最大值的二分查找平均只需 O(log N) 次比较(N是槽数),就能锁定唯一可能的分组。最小值可能需要额外线性探测或回溯,增加开销。
  • ​​插入与范围查询​​:在插入新记录时,使用最大值也更容易处理边界情况(如记录插入到分组末尾)。对于范围查询(如 key BETWEEN A AND B),最大值可以快速排除不相关的分组。
  • ​​单调性利用​​:主键值单调递增,使用最大值序列(如3,7,11,15)也单调递增,恰好匹配二分查找要求。

设计背后的权衡

  InnoDB 选择最大值是工程优化,牺牲少量内存(存储最大值)换取查询性能。分组大小(如默认每组6-8条记录)也进行了权衡,确保二分查找快速收敛的同时,分组内线性查找成本低。实际实现中,InnoDB 的页目录还处理了记录删除、变长记录等复杂情况,但最大值原则保持了二分查找的效率。

总之,槽记录分组最大主键值而非最小值,是为了使二分查找在页内定位记录时更高效、直接,减少了歧义和额外操作,从而提升整体查询性能。

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

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

相关文章

世赛背景下,中职物联网应用与服务赛项实训解决方案

一、世赛背景与物联网应用赛项概述 1.1 世赛发展历程及对中职教育的影响 世界技能大赛&#xff08;WorldSkills Competition&#xff0c;简称世赛&#xff09;自1950年创立以来&#xff0c;已经成为全球范围内展示职业技能水平的重要赛事。截至2024年&#xff0c;世赛已成功举…

【攻防篇】解决:阿里云docker 容器中自动启动xmrig挖矿-- 实战

文章目录 场景一、问题二、原因三、解决方案1、控制台处理2、 [清除与防护](https://blog.csdn.net/ladymorgana/article/details/148921668?spm1001.2014.3001.5501)1. 紧急处理&#xff1a;停止挖矿进程2. 清理被感染的容器3. 防护措施&#xff1a;防止再次被入侵4. 排查入侵…

飞算智造JavaAI:智能编程革命——AI重构Java开发新范式

文章目录 引言&#xff1a;当传统Java开发遇上AI一、技术架构解析1.1 核心架构图1.2 关键技术栈 二、实战演示&#xff1a;从需求到代码的全AI辅助2.1 场景&#xff1a;电商优惠券系统开发2.2 代码生成实例2.3 智能调试演示 三、与传统开发模式对比测试3.1 基准测试数据3.2 典型…

[特殊字符] 分享裂变新姿势:用 UniApp + Vue3 玩转小程序页面分享跳转!

在如今流量成本日益攀升的移动互联网时代&#xff0c;"用户分享拉新" 成为了增长的重要策略。而微信小程序作为天然具备社交传播力的平台&#xff0c;提供了较完善的分享机制支持。本文将从实战角度出发&#xff0c;手把手教你如何使用 uni-app Vue3 构建一个支持「…

[创业之路-458]:企业经营层 - 蓝海战略 - 重构价值曲线、整合产业要素、创造新需求

“重构价值曲线、整合产业要素、创造新需求”是蓝海战略中实现价值创新的核心路径&#xff0c;它们构成了一个从内部优化到外部协同&#xff0c;再到市场颠覆的完整逻辑链条。以下从理论框架、实践方法和企业案例三个维度展开分析&#xff1a; 一、重构价值曲线&#xff1a;打…

慢查询引发对mysql索引的探索

目录 一、索引分类 1.1 聚簇索引结构 1.2 非聚簇索引(二级索引) 1.3 主键索引 1.4 唯一索引 1.5 普通索引 1.6 前缀索引 1.7 联合索引 1.8 索引下推 1.9 索引区分度 二、优化索引的方法 2.1 索引的特点 2.2 适合创建索引的情况 2.3 不适合创建索引的情况 2.4 优…

启用不安全的HTTP方法

背景&#xff1a; 今天被安全检测出一个这样的问题&#xff1a;启用不安全的HTTP方法。DELETE方法是用来调试web服务器连接的http方式&#xff0c;支持该方式的服务器文件可能被非法删除&#xff1b;PUT方法用来向服务器提交文件&#xff1b;TRACE方法本用于客户端测试到服务器…

fvcom 水深文件dep制作

fvcom 水深文件dep制作 fvcom 水深文件dep制作20250630 本次案例网格和水深展示 vv image Figure 1 Model domain 本次制作其它驱动文件的输入文件为yellowsea.2dm 格式2dm; 文件内容格式详细介绍参考&#xff1a; https://www.xmswiki.com/wiki/SMS:2D_Mesh_Files_*.2dm …

ViewModel是EventFlow-State映射

ViewModel负责组装界面状态State。引发State变换的原因有很多&#xff0c;比如用户点击某个按钮&#xff0c;一次网络请求受到应答&#xff0c;一次本地数据库查询返回结果等等。因此ViewModel是根据各种事件生成State的对象&#xff0c;换句话说&#xff0c;是一个从多个事件流…

javaweb Day2

PreparedStatement作用: 预编译SQL语句并执行: 预防SQL注入问题 SQL注入:SQL注入是通过操作输入来修改事先定义好的SQL语句&#xff0c;用以达到执行代码对服务器进行攻击的方法。

Java项目:基于SSM框架实现的中学教学管理系统【ssm+B/S架构+源码+数据库+毕业论文+开题报告】

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本景海中学教学管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

JVM调优实战 Day 15:云原生环境下的JVM配置

【JVM调优实战 Day 15】云原生环境下的JVM配置 文章标签 jvm调优, 云原生, Java性能优化, JVM参数配置, 容器化部署, Kubernetes, Docker, JVM在云原生中的应用 文章简述 随着云原生技术的普及&#xff0c;Java 应用越来越多地运行在容器&#xff08;如 Docker&#xff09;和…

数据结构day7——文件IO

一、标准 IO 的起源与概念 标准 IO&#xff08;Standard Input/Output&#xff09;是由 Dennis Ritchie 在 1975 年设计的一套 IO 库&#xff0c;后来成为 C 语言的标准组成部分&#xff0c;并被 ANSI C 所采纳。它是对底层文件 IO 的封装&#xff0c;提供了更便捷、可移植的文…

6.Docker部署ES+kibana

部署ES&#xff08;Elasticsearch&#xff09;kibana 1.ES暴露的端口很多 2.ES十分消耗内存 3.ES的数据一般需要挂载出去&#xff0c;放在安全目录&#xff08;挂载) elastic 前往官方手册 1.下载运行elasticsearch的 docker run -d --name elasticsearch --net somenet…

使用mysqldump对mysql数据库进行备份

目录 1软件说明 2语法格式 3备份流程 3.1只备份指定数据库中表和数据 3.1.1准备目录 3.1.2备份db1数据库里面的所有表信息 3.1.3还原备份 3.2备份数据库结构 3.2.1备份db1数据库的结构和数据 3.2.2还原数据库 3.3备份所有数据库 3.3.1备份数据库 3.3.2还原数据库 1…

vue3路由跳转打开新页面

Vue3 路由跳转打开新页面的方法 在 Vue3 中&#xff0c;有几种方法可以实现路由跳转时打开新页面&#xff1a; 1. 使用 router.resolve 方法 import { useRouter } from vue-routerconst router useRouter()const openNewPage (path) > {const resolved router.resolv…

SeaTunnel 社区 2 项目中选“开源之夏 2025”,探索高阶数据集成能力!

Apache SeaTunnel 社区在“开源之夏 2025”中再传捷报&#xff0c;共有两个项目成功入选&#xff0c;聚焦于 Flink CDC schema 支持与元数据管理的生态扩展方向&#xff0c;体现出 SeaTunnel 在实时数据集成和平台化能力构建上的深入布局。 中选项目与学生如下&#xff1a; 《…

Neo4j无法建立到 localhost:7474 服务器的连接出现404错误

一、确认中文路径问题&#xff08;核心原因&#xff09; 安装路径包含中文&#xff0c;可能导致 Windows 命令行或 Neo4j 解析路径时出错。 解决方法&#xff1a; 重新安装 Neo4j 到英文路径&#xff08;推荐&#xff09;&#xff1a; 将 Neo4j 解压或安装到不含中文的目录&a…

锂离子电池均衡拓扑综述

锂离子电池均衡拓扑综述 一、引言 锂离子电池因其高能量密度、长循环寿命等优点&#xff0c;在电动汽车、储能系统等领域得到了广泛应用。然而&#xff0c;电池组在使用过程中&#xff0c;由于电池个体差异、充放电管理等因素&#xff0c;会出现荷电状态&#xff08;SOC&…

[面试] 手写题-浅拷贝,深拷贝

浅拷贝 // 浅拷贝 function shallow(obj) {const newObj {}for (const key in obj) {// 保证 key 不是原型的属性if (obj.hasOwnProperty(key)) {newObj[key] obj[key]}}return newObj }深拷贝 递归 O(n^2) // 深拷贝 function deepClone(obj {}) {// 如果传入的是 null&am…