mysql 存储结构的进化之路

news2025/2/25 8:16:54

文章目录

  • 前言
  • 一、线性结构
  • 二、二叉树(BST)
  • 三、平衡二叉树(AVL)
  • 四、多路平衡查找树(B Tree)
  • 五、加强版多路平衡查找树(B+ Tree)
  • 总结


前言

树形结构是一种具有层次关系的数据结构,它通常用于表示具有父子关系或层级关系的数据。在计算机科学中,树形结构广泛应用于文件系统、数据库索引、XML/JSON解析、搜索引擎索引等场景。通过不断进化,mysql最后构造出B+Tree作为存储结构。


一、线性结构

线性结构(如数组、链表)是计算机科学中最基础的数据结构,它们以线性方式组织数据。然而,当面对更为复杂的问题时,线性结构的局限性逐渐显露。
在这里插入图片描述
为了解决线性结构的局限性,计算机科学家开始探索多维的数据组织方式,树形结构应运而生。树形结构通过分层和分支的方式,能够更准确地模拟现实世界中的复杂关系。

二、二叉树(BST)

二叉树的特征
左子树所有的节点都小于父节点,右子树所有的节点都大于父节点。投影到平面以后,就是一个有序的线性表。
在这里插入图片描述
但是二叉查找树有一个问题:
就是它的查找耗时是和这棵树的深度相关的,在最坏的情况下时间复杂度会退化成O(n)
假设插入的数字为 2-4-6-8-12-16,此时的树形结构为
在这里插入图片描述
变成了一个一边倒的斜树,也就是链表,查找的时候和链表是一样的时间,复杂度变成O(n)。
造成这个现象是因为不能自动平衡,于是计算机科学家开始进行升级–平衡二叉树

三、平衡二叉树(AVL)

AVL的每个节点的左子树和右子树的高度差(平衡因子)只能是-1、0或1。查找、插入和删除操作的时间复杂度均为O(log n),其中n为树中节点的数量
例如下图,左侧的是AVL,右侧则不是AVL(高度差 > 1)
在这里插入图片描述
那如何保证二叉树的平衡呢?旋转操作与维护平衡

旋转操作:

  • 为了恢复平衡二叉树的平衡性,需要进行旋转操作。旋转操作包括左旋、右旋、左右双旋和右左双旋等。
  • 左旋操作将节点的右子树提升为新的根节点,并将原根节点降为新的左子节点。右旋操作则相反。
  • 双旋操作是在一次左旋或右旋的基础上再进行一次右旋或左旋,以恢复树的平衡性。

维护平衡:

  • 在进行插入或删除操作时,需要实时检查树的平衡性。一旦发现不平衡,就立即找到最小不平衡子树,并对其进行旋转操作以恢复平衡。
  • 旋转操作不仅改变了节点的位置关系,还调整了节点的平衡因子和子树的高度。

例如:下面的树插入了14后,深度差大于1,此时需要右旋再左旋来生成新的AVL
在这里插入图片描述
代码实现:

class TreeNode {
    int key;
    int height;
    TreeNode left, right;

    TreeNode(int d) {
        key = d;
        height = 1;
    }
}

class AVLTree {
    TreeNode root;

    // 右旋
    TreeNode rightRotate(TreeNode y) {
        TreeNode x = y.left;
        TreeNode T2 = x.right;

        // 进行右旋
        x.right = y;
        y.left = T2;

        // 更新高度
        y.height = Math.max(height(y.left), height(y.right)) + 1;
        x.height = Math.max(height(x.left), height(x.right)) + 1;

        // 返回新的根
        return x;
    }

    // 左旋
    TreeNode leftRotate(TreeNode x) {
        TreeNode y = x.right;
        TreeNode T2 = y.left;

        // 进行左旋
        y.left = x;
        x.right = T2;

        // 更新高度
        x.height = Math.max(height(x.left), height(x.right)) + 1;
        y.height = Math.max(height(y.left), height(y.right)) + 1;

        // 返回新的根
        return y;
    }

    // 获取高度
    int height(TreeNode N) {
        if (N == null)
            return 0;

        return N.height;
    }

    // 获取平衡因子
    int getBalance(TreeNode N) {
        if (N == null)
            return 0;

        return height(N.left) - height(N.right);
    }

    // 插入节点
    TreeNode insert(TreeNode node, int key) {
        // 典型的BST插入
        if (node == null)
            return (new TreeNode(key));

        if (key < node.key)
            node.left = insert(node.left, key);
        else if (key > node.key)
            node.right = insert(node.right, key);
        else // 重复键
            return node;

        // 更新当前节点的高度
        node.height = 1 + Math.max(height(node.left), height(node.right));

        // 获取平衡因子
        int balance = getBalance(node);

        // 如果当前节点失衡,则进行旋转

        // 左左情况
        if (balance > 1 && key < node.left.key)
            return rightRotate(node);

        // 右右情况
        if (balance < -1 && key > node.right.key)
            return leftRotate(node);

        // 左右情况
        if (balance > 1 && key > node.left.key) {
            node.left = leftRotate(node.left);
            return rightRotate(node);
        }

        // 右左情况
        if (balance < -1 && key < node.right.key) {
            node.right = rightRotate(node.right);
            return leftRotate(node);
        }

        // 返回(未修改的)节点指针
        return node;
    }

    // 查找节点(递归)
    boolean search(TreeNode root, int key) {
        // 基本情况:空树或树中只有一个节点
        if (root == null || root.key == key)
            return root != null;

        // 如果键小于根节点的键,则递归地在左子树中查找
        if (root.key > key)
            return search(root.left, key);

        // 否则递归地在右子树中查找
        return search(root.right, key);
    }

    // 插入新键
    void insert(int key) {
        root = insert(root, key);
    }

    // 查找键
    boolean search(int key) {
        return search(root, key);
    }

    // 主函数(测试)
    public static void main(String[] args) {
        AVLTree tree = new AVLTree();

        /* 构造树
              50
           /     \
          30      70
         /  \    /  \
        20   40  60   80 */
        tree.insert(50);
        tree.insert(70);
        tree.insert(30);
        tree.insert(20);
        tree.insert(80);
        tree.insert(40);
        tree.insert(60);

        // 查找键
        System.out.println("查找 20: " + tree.search(20));
        System.out.println("查找 30: " + tree.search(30));
        System.out.println("查找 100: " + tree.search(100));
    }
}

假设使用该结构作为数据存储结构的话,AVL如下所示
在这里插入图片描述
索引必须要存你建立索引的字段的值,对应关键字,比如id的值。还要存完整记录在磁盘上的地址,对应数据区。由于AVL树是二叉树,所以还要额外地存储左右子节点的指针。

  • 当我们用树的结构来存储索引的时候,访问一个节点就要跟磁盘之间发生一次 I0 操作。InnoDB 操作磁盘的最小的单位是一页(或者叫一个磁盘块),大小是16K(16384字节),一个节点只放一个索引和数据地址,就会很浪费资源。
  • 比如上面这张图,我们一张表里面有6条数据,当我们查询id=8的时候,要查询两个子节点,就需要跟磁盘交互3次,假设一次磁盘IO需要10ms, 6条数据就需要30ms,如果我们有上亿数据呢,那时间很很耗时。如果查询的数据是个范围,那这个时间又是成倍的增长。

所以这样的结构作为索引,是不太合理的。是需要去优化的。
我们把问题点列出来

  • 1、节点保存的数据少,浪费资源 -> 让节点保存更多数据
  • 2、树的深度太大,IO遍历太多 -> 节点保存的数据变多,那节点的分叉也可以变多,每个分叉后的节点又能保存数据,这样的话深度就大大减少

四、多路平衡查找树(B Tree)

B树是一种多路平衡查找树,广泛用于数据库和文件系统等需要动态排序数据结构的系统中。它以其高效的插入、删除、查找操作而著称,特别适合磁盘存储系统,可以有效减少磁盘I/O操作次数。有一个比较明显的特点:分叉数(路数)永远比关键字数多1

B树的定义和特性

  • 阶(Order):
    B树的阶(order)是树的重要特性,通常用字母m表示。阶m表示每个节点最多有m个子节点。

  • 节点的关键字(Keys):
    每个节点最多可以有m-1个关键字,最少有⌈m/2⌉-1个关键字(根节点可以例外)。

  • 节点的子节点数量(Children):
    非叶子节点至少有⌈m/2⌉个子节点,至多有m个子节点。如果一个节点有n个关键字,则它恰好有n+1个子节点。

  • 排序特性:
    设x是一个节点,x中的关键字按顺序排列:K1, K2, …, Kn。则对于x的子节点:C1, C2, …, Cn+1,有所有节点C1中的关键字 < K1,所有节点C2中的关键字在K1和K2之间,以此类推。

  • 平衡性:
    B树是一种自平衡树,即从根节点到任意一个叶子节点的路径长度都相同。

B树的基本操作

1、搜索

搜索某个关键字从根节点开始:

  • 在当前节点中找关键字,找到则结束搜索。
  • 否则,如果未到叶子节点,根据关键字的大小找到下一层子节点继续搜索。

2、插入

向B树中插入关键字,需保持树的平衡性:

  • 从根节点开始找到正确的叶子节点位置。
  • 如果叶子节点未满,直接插入。
  • 如果叶子节点已满,则进行节点分裂,将中间关键字提升到父节点。
    这可能导致父节点分裂,递归向上处理,可能导致树的高度增加。

3、删除
从B树中删除关键字需要分几种情况:

  • 在叶子节点删除关键字:
    直接删除,如果节点关键字数少于最小值,则需要借位或合并处理。

  • 在内部节点删除关键字:
    找到前驱或后继关键字替换之,并递归删除前驱或后继关键字,类似于二叉查找树的删除。
    采取借位或合并策略以保持树的平衡。

B树的优势

Mysql为了更好的利用磁盘的预读能力,把一个Page页的大小设置为16k,也就是一个节点(磁盘块)的大小是16K。也就是说在进行一次磁盘IO时,会把一个节点(16K)的索引加载到内存中。假设我们设置的一个表的索引字段类型是int,也就是4个字节,如果每个关键字对应的数据区(data)也是4个字节。
在不考虑子节点引用的情况下,那么在B Tree中,每个阶段大概能存储的关键字数量是:
( 16 * 1024 ) / 4+4 = 2048 个关键字
在B Tree中,一共有2049路。 对于二叉树来说,三层高度最多可以保存7个关键字,而对于这种2001路的B树,三层高度能够保存的关键字数远远大于二叉树,因此B Tree用来存储索引非常合适。
在这里插入图片描述

五、加强版多路平衡查找树(B+ Tree)

在Mysql的InnoDB引擎中,并没有使用B Tree作为索引的存储结构,而是使用B+Tree!它的存储结构如下图所示。
在这里插入图片描述
B+ Tree相比B Tree,做了以下几个方面的优化:

  1. B树的路数和关键字的个数的关系不再成立了,数据检索规则采用的是左闭合区间,路数和关键个数关系为1比1
  2. B+Tree的根节点和枝节点中都不会存储数据,只有叶子节点才存储数据,并且每个叶子节点都会增加一个指针指向响铃的叶子节点,形成一个有序链表结构。

在这样的几个B+ Tree结构下,假设我们要检索x=1的数据,那么检索的规则是:

  • 取出跟磁盘块, 加载1/26/66三个关键字。
  • x<=1, 取出磁盘块2,加载1/10/20三个关键字。
  • x<=1, 取出叶子节点,加载1/8/9三个关键字。此时已经达到了叶子节点,命中1, 所以只需要加载对应1的数据内容(或者内容地址)即 可!

B+Tree特性带来的优势:

  1. 它是B Tree的变种,B Tree能解决的问题,它都能解决。B Tree解决的两大问题是什么?(每个节点存储更多关键字;路数更多)
  2. 扫库、扫表能力更强(如果我们要对表进行全表扫描,只需要遍历叶子节点就可以了,不需要遍历整棵B+Tree拿到所有的数据)
  3. B+Tree的磁盘读写能力相对于B Tree来说更强(根节点和枝节点不保存数据区,所以一个节点可以保存更多的关键字,一盘加载的关键字更多)
  4. 排序能力更强(因为叶子节点上有下一个数据区的指针,数据形成了链表)
  5. 效率更加稳定(B+Tree永远是在叶子节点拿到数据,所以IO次数是稳定的)

由于在B+ Tree中,每个节点不存存储数据区,只需要存储键值+指针,使得 B+ Tree在每个节点存储的路数更多。
假设索引字段+指针大小一共是16个字节,那么一个Page页(一个节点)能存储1000个这样的单元(键值+指针)。
假设一条记录是16bytes,一个叶子节点(一页)可以存储10条记录!
当数的深度是2的时候,就有1000^2个叶子节点,可存储的数据为1000 x 1000 x 10 =10000000(千万)
也就是说在InnoDB中B+树的深度在3层左右,就能满足千万级别的数据存储。


总结

架构的不断升级带来了更多的性能提升,这点值得我们参考与提升

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

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

相关文章

高速定向广播声光预警系统赋能高速安全管控

近年来&#xff0c;高速重大交通事故屡见不鲜&#xff0c;安全管控一直是高速运营的重中之重。如何利用现代化技术和信息化手段&#xff0c;创新、智能、高效的压降交通事故的发生概率&#xff0c;优化交通安全管控质量&#xff0c;是近年来交管部门的主要工作&#xff0c;也是…

【机器学习】CatBoost 模型实践:回归与分类的全流程解析

一. 引言 本篇博客首发于掘金 https://juejin.cn/post/7441027173430018067。 PS&#xff1a;转载自己的文章也算原创吧。 在机器学习领域&#xff0c;CatBoost 是一款强大的梯度提升框架&#xff0c;特别适合处理带有类别特征的数据。本篇博客以脱敏后的保险数据集为例&#x…

游戏引擎学习第25天

Git: https://gitee.com/mrxiao_com/2d_game 今天的计划 总结和复述&#xff1a; 这段时间的工作已经接近尾声&#xff0c;虽然每次编程的时间只有一个小时&#xff0c;但每一天的进展都带来不少收获。尽管看起来似乎花费了很多时间&#xff0c;实际上这些日积月累的时间并未…

AI开发:生成式对抗网络入门 模型训练和图像生成 -Python 机器学习

阶段1&#xff1a;GAN是个啥&#xff1f; 生成式对抗网络&#xff08;Generative Adversarial Networks, GAN&#xff09;&#xff0c;名字听着就有点“对抗”的意思&#xff0c;没错&#xff01;它其实是两个神经网络互相斗智斗勇的游戏&#xff1a; 生成器&#xff08;Gene…

040集——CAD中放烟花(CAD—C#二次开发入门)

效果如下&#xff1a; 单一颜色的烟花&#xff1a; 渐变色的火花&#xff1a; namespace AcTools {public class HH{public static TransientManager tm TransientManager.CurrentTransientManager;public static Random rand new Random();public static Vector3D G new V…

JavaScript实现tab栏切换

JavaScript实现tab栏切换 代码功能概述 这段代码实现了一个简单的选项卡&#xff08;Tab&#xff09;切换功能。它通过操作 HTML 元素的类名&#xff08;class&#xff09;来控制哪些选项卡&#xff08;Tab&#xff09;和对应的内容板块显示&#xff0c;哪些隐藏。基本思路是先…

【天地图】HTML页面实现车辆轨迹、起始点标记和轨迹打点的完整功能

目录 一、功能演示 二、完整代码 三、参考文档 一、功能演示 运行以后完整的效果如下&#xff1a; 点击开始&#xff0c;小车会沿着轨迹进行移动&#xff0c;点击轨迹点会显示经纬度和时间&#xff1a; 二、完整代码 废话不多说&#xff0c;直接给完整代码&#xff0c;替换…

HCIA笔记6--路由基础与静态路由:浮动路由、缺省路由、迭代查找

文章目录 0. 概念1.路由器工作原理2. 跨网访问流程3. 静态路由配置4. 静态路由的应用场景4.1 路由备份4.2 浮动路由4.3 缺省路由 5. 迭代路由6 问题6.1 为什么路由表中有的下一跳的地址有接口&#xff1f;6.2 个人电脑的网关本质是什么&#xff1f; 0. 概念 自治系统&#xff…

20241129解决在Ubuntu20.04下编译中科创达的CM6125的Android10出现找不到库文件libncurses.so.5的问题

20241129解决在Ubuntu20.04下编译中科创达的CM6125的Android10出现找不到库文件libncurses.so.5的问题 2024/11/29 21:11 缘起&#xff1a;中科创达的高通CM6125开发板的Android10的编译环境需要。 vendor/qcom/proprietary/commonsys/securemsm/seccamera/service/jni/jni_if.…

Matlab搜索路径添加不上

发现无论是右键文件夹添加到路径&#xff0c;还是在“设置路径”中专门添加&#xff0c;我的路径始终添加不上&#xff0c;导致代码运行始终报错&#xff0c;后来将路径中的“”加号去掉后&#xff0c;就添加成功了&#xff0c;经过测试&#xff0c;路径中含有中文也可以添加成…

自由学习记录(28)

C# 中的流&#xff08;Stream&#xff09; 流&#xff08;Stream&#xff09;是用于读取和写入数据的抽象基类。 流表示从数据源读取或向数据源写入数据的矢量过程。 C# 中的流类是从 System.IO.Stream 基类派生的&#xff0c;提供了多种具体实现&#xff0c;每种实现都针对…

Redis3——线程模型与数据结构

Redis3——线程模型与数据结构 本文讲述了redis的单线程模型和IO多线程工作原理&#xff0c;以及几个主要数据结构的实现。 1. Redis的单线程模型 redis6.0之前&#xff0c;一个redis进程只有一个io线程&#xff0c;通过reactor模式可以连接大量客户端&#xff1b;redis6.0为了…

使用playwright自动化测试时,npx playwright test --ui打开图形化界面时报错

使用playwright自动化测试时&#xff0c;npx playwright test --ui打开图形化界面时报错 1、错误描述&#xff1a;2、解决办法3、注意符号的转义 1、错误描述&#xff1a; 在运行playwright的自动化测试项目时&#xff0c;使用npm run test无头模式运行正常&#xff0c;但使用…

深度学习模型:门控循环单元(GRU)详解

本文深入探讨了门控循环单元&#xff08;GRU&#xff09;&#xff0c;它是一种简化版的长短期记忆网络&#xff08;LSTM&#xff09;&#xff0c;在处理序列数据方面表现出色。文章详细介绍了 GRU 的基本原理、与 LSTM 的对比、在不同领域的应用以及相关的代码实现&#xff0c;…

用html+jq实现元素的拖动效果——js基础积累

用htmljq实现元素的拖动效果 效果图如下&#xff1a; 将【item10】拖动到【item1】前面 直接上代码&#xff1a; html部分 <ul id"sortableList"><li id"item1" class"w1" draggable"true">Item 1</li><li …

单片机学习笔记 12. 定时/计数器_定时

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…

【乐企文件生成工程】搭建docker环境,使用docker部署工程

1、自行下载docker 2、自行下载docker-compose 3、编写Dockerfile文件 # 使用官方的 OpenJDK 8 镜像 FROM openjdk:8-jdk-alpine# 设置工作目录 WORKDIR ./app# 复制 JAR 文件到容器 COPY ../lq-invoice/target/lq-invoice.jar app.jar # 暴露应用程序监听的端口 EXPOSE 1001…

React基础知识三 router路由全指南

现在最新版本是Router6和Router5有比较大的变化&#xff0c;Router5和Router4变化不大&#xff0c;本文以Router6的写法为主&#xff0c;也会对比和Router5的不同。比较全面。 安装路由 npm i react-router-dom基本使用 有两种Router&#xff0c;BrowserRouter和HashRouter&…

【C#】书籍信息的添加、修改、查询、删除

文章目录 一、简介二、程序功能2.1 Book类属性&#xff1a;方法&#xff1a; 2.2 Program 类 三、方法&#xff1a;四、用户界面流程&#xff1a;五、程序代码六、运行效果 一、简介 简单的C#控制台应用程序&#xff0c;用于管理书籍信息。这个程序将允许用户添加、编辑、查看…

打造去中心化交易平台:公链交易所开发全解析

公链交易所&#xff08;Public Blockchain Exchange&#xff09;是指基于公有链&#xff08;如以太坊、波场、币安智能链等&#xff09;建立的去中心化交易平台。与传统的中心化交易所&#xff08;CEX&#xff09;不同&#xff0c;公链交易所基于区块链技术实现资产交换的去中心…