InnoDB索引的原理

在鹅厂后端开发一面,我遇到了如题这样一个比较宽泛的问题,当时可能只是背了相关概念,对于索引的了解不是很深刻。
最近,我花了很大的功夫去深入了解MySQL的索引。
下面是我的一些思考:
索引,对于InnoDB来说,索引通常是指一棵B+树(聚簇索引或二级索引)。

  • 对于聚簇索引来说,只是叶子节点存放的列包括主键和其他列,索引存放的是每个页中最小主键值和页号
  • 对于非聚簇索引来说,叶子节点存放的是 索引列 和主键值,非叶子节点存放的是最小主键值和页号+ 索引列的值(保证在B+树的同一层内节点的目录项记录除页号这个字段以外是唯一的)

原因是:InnoDB的B+树索引是有序的。对于复合索引(比如索引列a,b),如果有多条记录a,b值相同,InnoDB需要依靠主键值来唯一定位和排序。下面举一个具体的例子说明:

假设 数据库建表语句如下:

mysql> CREATE TABLE index_demo(-> c1 INT,-> c2 INT,-> c3 CHAR(1),-> PRIMARY KEY(c1)-> INDEX idx_c2 (c2)-> ) ROW_FORMAT = Compact;

主键是C1, 二级索引C2,假设表中的数据是
在这里插入图片描述
如果二级索引中目录项记录的内容只是 索引列 + 页号 的搭配的话,那么为 c2 列建立索引后的 B+ 树应该长这样
在这里插入图片描述
如果我们想新插入一行记录,其中 c1 、 c2 、 c3 的值分别是: 9 、 1 、 ‘c’ ,那么在修改这个为 c2 列建立的二级索引对应的 B+ 树时便碰到了个大问题:由于 页3 中存储的目录项记录是由 c2列 + 页号 的值构成的,页3 中的两条目录项记录对应的 c2 列的值都是 1 ,而我们新插入的这条记录的 c2 列的值也是 1 ,那我们这条新插入的记录到底应该放到 页4 中,还是应该放到 页5 中啊?答案是:无法判断。

为了让新插入记录能找到自己在那个页里,我们需要保证在B+树的同一层内节点的目录项记录除 页号 这个字段以外是唯一的。所以对于二级索引的内节点的目录项记录的内容实际上是由三个部分构成的:

  • 索引列的值
  • 主键值
  • 页号

也就是我们把 主键值 也添加到二级索引内节点中的目录项记录了,这样就能保证 B+ 树每一层节点中各条目录项记录除 页号 这个字段外是唯一的,所以我们为 c2 列建立二级索引后的示意图实际上应该是这样子的
在这里插入图片描述
这样我们再插入记录 (9, 1, ‘c’) 时,由于 页3 中存储的目录项记录是由 c2列 + 主键 + 页号 的值构成的,可
以先把新记录的 c2 列的值和 页3 中各目录项记录的 c2 列的值作比较,如果 c2 列的值相同的话,可以接着比较
主键值,因为 B+ 树同一层中不同目录项记录的 c2列 + 主键 的值肯定是不一样的,所以最后肯定能定位唯一的
一条目录项记录,在本例中最后确定新记录应该被插入到 页5 中。

索引同层的非叶子结点间,是否也用双线链表维系呢?

是的,如果询问AI 大模型,大部分都是回答没有,这显然是错误的,为此我查阅了相关Mysql 源码的实现storage/innobase/include/fil0fil.h 的第461 行

#define FIL_PAGE_PREV		8	/*!< 如果页面存在一个“自然”的前驱页面,则记录其偏移量。否则,该字段为FIL_NULL。对于BLOB页面,此字段未设置,因为BLOB页面是以单链表形式存储的。另请参见FIL_PAGE_NEXT。 */#define FIL_PAGE_NEXT		12	/*!< 如果存在页面的“自然”后继节点,则记录其偏移量。否则为FIL_NULL。相同PAGE_LEVEL的B树索引页(FIL_PAGE_TYPE包含FIL_PAGE_INDEX)通过FIL_PAGE_PREV和FIL_PAGE_NEXT按照每页上最小用户记录的排序顺序维护为双向链表。 */

相同PAGE_LEVEL的B树索引页,(FIL_PAGE_TYPE包含FIL_PAGE_INDEX),通过FIL_PAGE_PREVFIL_PAGE_NEXT,按照每页上最小用户记录的排序顺序,维护为双向链表。

非叶子结点各层的双向链表,连起来起了什么作用呢?

深入了解 源码后,在storage/innobase/btr/btr0btr.cc 中找到了相关的代码实现

  1. 支持范围扫描操作
// 在进行范围扫描时会使用这些链接:
if (left_page_no != FIL_NULL) {prev_block = btr_block_get(page_id_t(space, left_page_no), block->page.size,RW_X_LATCH, index, mtr);
}if (next_page_no != FIL_NULL) {next_block = btr_block_get(page_id_t(space, next_page_no), block->page.size,RW_X_LATCH, index, mtr);
}
  1. 维护B树结构的完整性
// 在页面分裂时需要维护这些链接关系:
btr_page_set_next(lower_page, lower_page_zip, upper_page_no, mtr);
btr_page_set_prev(upper_page, upper_page_zip, lower_page_no, mtr);if (direction != FSP_DOWN) {btr_page_set_next(upper_page, upper_page_zip, next_page_no, mtr);
} 
  1. 优化查询性能
// 在查询时可以快速定位到相邻页面:
static
void
btr_cur_prefetch_siblings(buf_block_t*    block)
{page_t* page = buf_block_get_frame(block);ulint left_page_no = fil_page_get_prev(page);ulint right_page_no = fil_page_get_next(page);// 预读相邻页面以提高性能if (left_page_no != FIL_NULL) {buf_read_page_background(...);}if (right_page_no != FIL_NULL) {buf_read_page_background(...);}
}
  1. 支持页面合并操作
// 在删除操作导致页面需要合并时:
void
btr_discard_page(btr_cur_t*  cursor,mtr_t*      mtr)
{// 获取相邻页面号left_page_no = btr_page_get_prev(buf_block_get_frame(block), mtr);right_page_no = btr_page_get_next(buf_block_get_frame(block), mtr);if (left_page_no != FIL_NULL) {// 与左侧页面合并merge_block = btr_block_get(...);}
}
  1. 数据一致性检查
// 在验证索引时会检查相邻页面的记录顺序:
if (!dict_index_is_spatial(index)&& cmp_rec_rec(rec, right_rec,offsets, offsets2, index, false) >= 0) {// 如果相邻页面的记录顺序不正确,报错btr_validate_report2(index, level, block, right_block);
}

每个页对应一个目录项,每个目录项包括下边两个部分:

聚簇索引

在这里插入图片描述

  • 页的用户记录中最小的主键值,我们用 key 来表示。
  • 页号,我们用page_no 表示。

聚簇索引是一种特殊的B+ 树

满足以下 两个特点:

  1. 使用记录主键值的大小进行记录和页的排序,这包括三个方面的含义
    • 页内的记录是按照主键的大小顺序排成一个单向链表。
    • 各个存放用户记录的页也是根据页中用户记录的主键大小顺序排成一个双向链表。
    • 存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表。
  2. B+ 树的叶子节点存储的是完整的用户记录。
    所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)

之前我们说过索引的构造和用户记录页 很相似,那么InnoDB 怎么区分一条记录是普通的用户记录还是目录项记录呢?别忘了记录头信息里的record_type 属性,它的各个取值代表的意思如下:

  • 0 :普通的用户记录
  • 1 :目录项记录
  • 2 :最小记录
  • 3 :最大记录

参考:《从根上理解MySQL》

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

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

相关文章

FormCalc 支持的编程语言和软件

FormCalc 是一种专为 PDF 表单计算设计的脚本语言&#xff0c;主要应用于 Adobe 生态及 SAP 相关工具。以下是支持 FormCalc 的主要软件和平台&#xff1a; 1. Adobe LiveCycle Designer&#xff08;最佳支持&#xff09; 原生支持&#xff1a;FormCalc 是 LiveCycle Designe…

unity 为什么不切片 Sprite.rect 与Sprite.textureRect的值还不一样

一。测试代码&#xff1a; 二。发现Debug不一样的原因 与解决方案&#xff1a; 下图右边所示&#xff1a; 网格类型默认为紧密 在 Unity 中&#xff0c;纹理导入时可能存在自动的偏移和裁剪设置。即便你没有手动切片&#xff0c;Unity 可能会根据纹理的导入设置&#xff0c;对…

超预期!淘宝闪购提前开放全国全量,联合饿了么扭转外卖战局

饿了么由守转攻。 作者|景行 编辑|杨舟 淘宝饿了么&#xff0c;终于落子&#xff0c;“淘宝闪购”&#xff0c;横空出世&#xff0c;仅仅2天&#xff0c;业务加速。 4月30日上午&#xff0c;当外卖战场陷入沉寂时&#xff0c;淘宝宣布将即时零售业务“小时达”升级为“淘宝闪…

minio相关面试问题和参考答案

可以考虑以下几个方面&#xff1a; MinIO概述与特性MinIO与其他对象存储的比较MinIO的使用场景MinIO的API与SDKMinIO的安全性与权限管理MinIO的性能优化 以下是一些相关的面试技术问题及其参考回答&#xff1a;具体如下&#xff1a; MinIO的主要特性包括&#xff1a; 高性能&am…

加载ko驱动模块:显示Arm版本问题解决!

1、问题 驱动模块加载&#xff0c;使用命令&#xff1a;modprobe chrdevbase.ko 时出现&#xff1a; hrdevbase: version magic 4.1.15 SMP preempt mod_unload modversions ARMv6 p2v8 ’ should be 4.1.15 SMP preempt mod_unload modversions ARMv7 p2v8 ’ ———————…

【论文阅读一】掌握高效阅读法,开启学术研究新旅程:S. Keshav教授论文阅读的三遍法

文章目录 一、三遍阅读法1. 初读&#xff1a;10分钟&#xff1a;宏观把握&#xff0c;快速筛选2. 第二遍&#xff1a;1个小时&#xff1a;更仔细的阅读&#xff0c;了解文中论点3. 第三遍&#xff1a;深入理解&#xff0c;注重细节&#xff0c;挑战假设 二、运用三遍阅读法进行…

3D Gaussian Splatting部分原理介绍和CUDA代码解读

本系列旨在帮助无CUDA代码经验的读者、以及3DGS的初学者理解代码逻辑。 3D GS论文原文链接&#xff1a;https://arxiv.org/abs/2308.04079 论文笔记链接&#xff1a;【论文笔记】3D Gaussian Splatting for Real-Time Radiance Field Rendering 【论文笔记】A Survey on 3D Ga…

【数据结构】--- 双向链表的增删查改

前言&#xff1a; 经过了几个月的漫长岁月&#xff0c;回头时年迈的小编发现&#xff0c;数据结构的内容还没有写博客&#xff0c;于是小编赶紧停下手头的活动&#xff0c;补上博客以洗清身上的罪孽 目录 前言&#xff1a; 概念&#xff1a; 双链表的初始化 双链表的判空 双链表…

Ubuntu如何查看硬盘的使用情况,以及挂载情况。

在Ubuntu中查看硬盘使用情况及挂载情况&#xff0c;可通过以下命令实现&#xff1a; 一、查看硬盘使用情况 df -h 显示所有挂载文件系统的磁盘空间使用情况&#xff08;含总容量、已用空间、可用空间等&#xff09;&#xff0c;输出结果以易读格式&#xff08;如GB、MB&#x…

Github 2025-05-02Java开源项目日报 Top9

根据Github Trendings的统计,今日(2025-05-02统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目9Android开源轻量级流媒体前端 创建周期:3158 天开发语言:Java协议类型:GNU General Public License v3.0Star数量:28641 个Fork数量…

linux学习——数据库API创建

一.API操作 1.int sqlite3_open(char *filename,sqlite3 **db) 功能&#xff1a;打开sqlite数据库 参数&#xff1a; filename:数据库文件路径 db:指向sqlite句柄的指针 &#xff08;splite3* db;&#xff09; 返回值…

Baklib内容中台落地实战指南

内容中台实施最佳路径 在构建企业级内容中台的实践中&#xff0c;架构设计与流程优化构成核心支撑框架。通过四库体系&#xff08;知识库、资源库、模板库、场景库&#xff09;的有机组合&#xff0c;企业可实现从知识沉淀到场景化应用的闭环管理。智能检索技术结合语义分析引…

【重走C++学习之路】26、类型转换

目录 一、C语言中的类型转换 二、C中的四个类型转换 2.1 static_cast 2.2 dynamic_cast 2.3 const_cast 2.4 reinterpret_cast 2.5 总结 结语 一、C语言中的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&a…

kotlin 过滤 filter 函数的作用和使用场景

1. filter 函数的作用 filter 是 Kotlin 集合操作中的一个高阶函数&#xff0c;用于根据指定条件从集合中筛选出符合条件的元素。 作用&#xff1a;遍历集合中的每个元素&#xff0c;并通过给定的 lambda 表达式判断是否保留该元素。返回值&#xff1a;一个新的集合&#xff…

安卓程序打包与发布

一 配置编译信息 二 创建密钥

LeetCode算法题 (移除链表元素)Day15!!!C/C++

https://leetcode.cn/problems/remove-linked-list-elements/description/ 一、题目分析 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 今天的题目非常好理解&#xff0c;也就是要删除…

Scrapy框架之【Scrapy-Redis】分布式爬虫详解

Scrapy-Redis 介绍 Scrapy-Redis 是一个基于 Redis 实现的 Scrapy 分布式爬虫组件。Scrapy 本身是一个强大的 Python爬虫框架&#xff0c;但它默认是单进程单线程的&#xff0c;在面对大规模数据抓取任务时效率不高。Scrapy-Redis 则解决了这一问题&#xff0c;它允许你将 Scra…

Gradio全解20——Streaming:流式传输的多媒体应用(3)——实时语音识别技术

Gradio全解20——Streaming&#xff1a;流式传输的多媒体应用&#xff08;3&#xff09;——实时语音识别技术 本篇摘要20. Streaming&#xff1a;流式传输的多媒体应用20.3 实时语音识别技术20.3.1 环境准备和开发步骤1. 环境准备2. ASR应用开发步骤&#xff08;基于Transform…

使用xlwings将两张顺序错乱的表格进行数据核对

有如下一个excel表&#xff0c;姓名列的内容相同&#xff0c;顺序不同&#xff1b;月薪有部分内容不同。 目的&#xff1a;要找出哪几条月薪不同。 通常的做法&#xff0c;要使用excel的高级筛选。 在此&#xff0c;使用xlwings实现&#xff0c;在不同的内容上涂色。 代码如…

2025大模型安全研究十大框架合集(10份)

2025大模型安全研究十大框架合集的详细介绍&#xff1a; Anthropic AI信任研究框架 Anthropic于2024年10月更新的《安全责任扩展政策》(RSP)&#xff0c;提出了一个灵活的动态AI风险治理框架。该框架规定当AI模型达到特定能力时&#xff0c;将自动升级安全措施&#xff0c;如…