Mysql 学习1

咱就是说,写这个东西纯纯是半功利性的深入挖掘Mysql, 但并不等于说我Mysql在这一阶段学的不认真

Mysql体系架构:

  1. (连接池: JDBC, Python,API 等等外部连接)

  2. 管理工具与服务: 系统管理,备份, Mysql集群等等

  3. SQL接口: DML, DDL , 接受SQL命令并返回查询接口

  4. Parser : 解析器,解析SQL语句成为语法树

  5. Optimizer 优化器, 对SQL 语句进行一定的优化分析

  6. (Cache 查看查询的语句,表,权限等是否缓存,加快速度) 已被废弃,与缓存匹配的方式还是HASH,匹配及其严格

  7. 存储引擎: 进行数据的存储的引擎,和文件系统进行交互

在这里插入图片描述

简单来讲, Mysql可以分为 连接层 - 》 Server层 -》 存储引擎层

show variables like "%max_connections%";  # 查看最大连接数
set global max_connections = XXX		 # 修改最大连接数

MYSQL的官方引擎:

InnoDB (默认引擎, 擅长处理大量的短事务, 应用最广) ,

MyISAM(5.1以前的默认引擎,数据处理有了可能错乱可能锁表,只适用于GIS),

Archive(只支持插入和Select 操作,所有数据会进行压缩,没有事务),

Blackhole(不保存数据,只更新日志,可以用于Mysql的复制功能,减少了数据的写入,只搞日志),

CSV (支持将CSV文件导入Mysql使用)

Memory (讲数据放到内存中,一个HashMap, 但是又Redis这种上位替代)

NDB集群引擎: 用于Mysql集群。

面经:InnoDB 与 MyISAM的差别是什么?

MyISAMInnoDB
主外键不支持支持
事务不支持支持
行表锁表锁,即使操作一条记录也会锁住整个表,不适合高并发索引查询行锁,不索引表级锁
缓存只缓存索引不缓存值缓存索引和值,对内存有较高要求。内存对性能的影响很大
表空间
关注点性能事务
默认安装YY

TIPS: 关于锁:https://blog.csdn.net/eternal_yangyun/article/details/101037977

锁一共有三种:全局锁,表级锁, 行锁

全局锁会让整个数据库处于只读状态:在做全局备份的时候,一帮将库射到只读状态防止其他请求数据的干扰等等

flush table with read lock;     	加锁
unlock tables;				   	   释放锁

表级锁又分表锁与元数据锁:

  • 表锁:在还没出现更细粒度的所得时候常用于处理并发问题。但是现在应该不太方便了

    lock tables table_name read/write		加锁
    unlock tables						   去锁
    
  • MDL 元数据锁:Mysql5,.5 后引入,不需要显示的使用,在访问一个表的时候会自动加上,保证读写的正确性。写锁与任何锁互斥。事务中的MDL锁是语句执行时开始申请,但语句结束后不会马上释放,等事务提交后释放

行锁: 引擎层进行实现的, 针对索引加的锁,而非针对记录,并且该索引不能失效,否则升级为表锁。在InnoDB事物中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事物提交了才会释放,这个就是两阶段锁协议;

MYSQL的目录与文件。我用docker装的mysql, 首先是大范围的find,结果呢很明显的集中在这几个位置:

  • /usr/share/doc 放置mysql 的一些文档说明的
  • /usr/bin 放置mysql的可执行文件与一些其他重要的文件的
    • mysql 可执行二进制文件
    • mysqld_safe 本质是一调用Mysql的高级个脚本在mysql的基础上加了一个监控进程,在mysql崩溃的时候重新调用一个mysql 并集中记录Mysql的出错信息https://www.cnblogs.com/jkin/p/10213027.html
    • mysqladmin 检查Mysql的配置与当前的服务状态。
    • Mysqldump 对数据库进行逻辑备份(将数据库当前的表状态以SQL语句的形式导出来)
  • /var/lib 日志存储位置,主要是放置Mysql生成的日志信息。

Mysql 启动:

# 虽然我一般是用这种最拉跨的方式
mysql -u user - p password
# 但是显然有更优雅与安全的方式(mysql_safe脚本)
https://www.cnblogs.com/lkxed/p/start-mysqld-with-mysqld_safe.html

关于mysql的辣么多参数请自行去官网查看。

mysql数据库会在配置文件的路径下创建文件夹。当然,我们一般是放在balabala data下面。我们可以使用刚刚的show variables查询datadir即可知道存放数据的路径。

mysql> show variables like '%datadir%';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| datadir       | /var/lib/mysql/ |
+---------------+-----------------+
1 row in set (0.01 sec)

在具体的库目录中都有响应表的frm, frm就是存储这些表的表结构的文件。而表数据根据不同的引擎存储在不同类型的我们称之为表空间的东西。表空间在InnoDB中会北环分为不同的页。

表空间的分级:首先是系统表空间:在文件上体现为ibdata。 这个文件会自动进行扩展。这个文件的好处是放在不同的磁盘上,多个磁盘并行写入加快读写速度。当然这玩应需要改我们的配置文件。

在数据库版本为5.57到5.66之间所有的表数据都存在系统表空间。在5.66之后出现了独立表空间

独立表空间是没创建一个表就会有相应的表空间,表名.frm 就是他的数据文件。(这个frm是innoDB特有的,其他引擎的文件名称可能会略有不同) 我在docker中找过了,对应的位置是/var/lib/mysql

Mysql除了表数据会生成文件之外还会生成日志文件: error.log(错误日志,docker里没有文件而是用stderr 意思是有错误直接输出) , slow_query.log ( 慢查询日志 , 用于优化) , query.log ( 查询日志,所有的查询信息,很消耗性能,默认不开启) , bin .log ( 二进制文件 , 用于将数据转存到其他数据库或平台上如elk, hbase等, 也适用于主从复制。 默认开启哦)

这些东西的开启方式: set global XXX = on;

MYSQL中的系统库: performance_schema, sys, information_schema, mysql, 复制信息表

数据库的事务:事务是数据库管理系统 ( DBMS) 执行过程中一个不可分的逻辑单元,由一个有限的数据库操作序列构成( 多个BML语句, select语句不包含事务) 事务有四大特性: 原子性 (Atomicity), 一致性 ( Consistency) , 持久性(Durability) , 隔离性 (Isolation) ( 并发执行的数据不能相互干扰,需要上锁OR或者说串行化执行)

事务并发,在牺牲一定隔离性的时候,有可能会导致数据的脏读。脏读,就是一个事务读取到了另一个事务修改但还未完全提交的数据

eg. 一个事务脏读的例子

事务1事务2
beginbegin
select a … return 1
set a = 2
select a … return 2
set a = a-1rollback

事务1读到a = 1 -> 事务 2设置a = 2 -> 事务1读到a = 2 -> 事务1设置a = a- 1 但是事务2回滚啦, 本来a应该是2- 1 = 1 但是2 是脏数据正确应该是 1 - 1 = 0

当然,除了脏读,上述例子还出现了不可重复读, 即两次读取同一数据的返回值不相同。初次之外,事务并发还会导致幻读:另一个事务将新记录添加到正在读取的事务中。(在原来的记录基础上多出了记录)eg.

事务1事务2
select a from b return 1
insert into b6 values(a,10)
select a from b return (1,10)

MySQL标准的隔离级别

隔离级别脏读重复读幻读
未提交读(Read Uncommit)
已提交读(Read Commited)
可重复读(Repeatable Read)
可串行化(serializable)

当然了,对于我这个底子的数据库看到隔离等级都不知道是啥就直接歇菜了。这里先放一篇大佬的博客镇楼:https://blog.csdn.net/baihualindesu/article/details/100375244。 Mysql设置事务隔离级别

#隔离级别,一般是可重复读
show variables like 'transaction_isolation'
# 设置隔离级别
set transaction isolation level XXX

拓展视图读: MVCC (Model Views Concurrency Control) , 锁定读:LBCC (Locked Base Concurrency Control) 这个我还没查资料,先放在这了。

MYSQL 事务的保存点。在一个事务执行的命令过多,同时不想完全回滚到之效率低下的情况下,可以考虑使用savepoint 保存点, 这样rollback不会完全回滚而会回到某个点, 语法如下

Save point point_name
Rollback to point_name

MySQL事务提交虽然一般用commit() 提交,但是还存在隐式提交,即即使没有使用Commit也会被提交事务。 eg. DDL data defination language 当出现 DDL语言的时候,之前所有的事物都会被提交,即隐式提交。 或者使用mysql系统库的时候也会进行隐式提交

TIPS: create/alert/drop 修改了数据库对象或结构的这种语法就叫DDL,当然知识对数据库表内数据进行select , insert这种并不是DDL, 而是DML, 即数据操纵语言。

数据库范式(当年数据库课折磨我的罪魁祸首之一):

范式的等级一共有:

1NF 2NF 3NF 巴斯-科德范式BCNF 第四范式4NF 第五范式5NF(又叫做完美范式) (我学的一般优化到第三范式就差不多了)

  • 1NF 每一列属性不可再分, 保证列的原子性,设计出来的表都是简单的二维表
  • 2NF 要求实体的属性完全依赖于主关键字。即,如果存在不依赖主关键字的属性应该被分割成一张新的table(去掉函数依赖)
  • 3NF 在2NF的基础上去掉传递依赖

反范式化设计,包括:冗余,缓存,汇总,计数器表,分库分表的查询。

冗余:将本在第三范式的数据表设置一定冗余,减少联合查询的次数从而达到提升性能的目的

缓存:把经常使用的数据进行缓存,不只是热门数据,在表中新建响应的缓存字段,(不过跟缓存相关大概用redis)

汇总:对于经常需要group_by的表新建group_by新建字段,并设置定时更新啥的方法保证数据有效(对及时性不要求太高),如业务报表等

计数器表:我感觉是Redis 的下位替代了,感觉没有用

分库分表的查询:保存数据的时候保存不同维度的信息,对一份记录记录多分数据

Mysql 字段优化: 原则 1. 简单的好 2. 空间占用少的好 3. 尽量避免NULL

关于NULL, 再Mysql 中任何字段都可以使用NUll 填充,说明Null是一种特殊的Mysql自定义的数据结构。有NUll存在会让Mysql的这一filed特殊化。NUll带来的影响: 1. 不支持部分查询, 如不支持Not Null, count统计。 2。 占用的空间更大, Null是一种特殊的数据结构,会导致空间的占用更多。 3. 任何与NUll有关的运算结构都是NUll, 增大了复杂性。

Mysql支持的字段类型:

  1. 整数型:

    Tinyint 1 bytes

    smallint 2 bytes

    int 4 bytes

    bigint 8 bytes

    所有的整数型支持 unsigned 无符号设置

  2. 小数类

    Float 4 bytes 不精确

    Double 8 bytes 不精确

    Decimal 支持存储65位10进制数 精确(从前面的要求也能看出来底层是字符串)

    TIPs Decimal对于银行等必须完全精确的地方很实用。但是有时候我们可以考虑使用几个Bigint 进行Decimal数据存储替换

  3. 字符串类型:

    Varchar nbytes + 2 bytes(多出来的2bytes是存储长度) 支持不定长

    char nbytes 定长

    BLOB/TEXT 容量较大 存储文件的结构,但底层是字符串。BLOB是2进制字符串流, TEXT是字符类型存储。

    BLOB/ TEXT 尽量单独拆除一张表不要放在表字段中,或者根本不要用

    tips: 必要时可以使用枚举类型代替字符串

    tips.tips. 关于varchar的容量不要太大防止分页的现象、关于分页请看后面

  4. 日期与时间类型: 略

Mysql 索引的定义: Index所以是帮助Mysql高校获取数据的数据结构

InnoDB 支持的索引类型:B+树索引, 全文索引, HASH索引(内部使用)

为什么Innodb默认数据索引使用B+树/ 为什么Hash索引不行? 因为再数据量过大的时候绝对会产生HASH冲突,采用链表法的多层索引耗费内存较大。同时不能较好的支持范围查找与数据排序(完全遍历,因为Hash乱序) Hashmap所以采用B+树索引。

拓展,Python中的dict dict本身就是Hashmap, 但是发生hashmap的时候并不是采用链表法进行存储,而是采用向下寻址法存储。此外再python 3.7 + 的版本中dict 有多维护了一张顺序表用来将dict从无序进阶为有序

所以说B+树索引就是传统意义上的索引。这是目前关系型数据库系统中最常用和最有效的索引。B+树是二叉查找树加上平衡二叉树以及B树而来

看来数据结构是逃不掉了从树 -> 二叉树 -> 二叉查找树 - > 平衡二叉树 -> B树 -> B+树 这样的顺序进阶

(学过数据结构的同学直接跳过吧,太麻烦了)

什么是树:在图论中有树和图的概念区分。树这种数据结构每个节点有零个或多个孩子,有且仅有一个没有父节点的节点(我们将之称作根节点)(多个根节点就是多棵树,或者说森林) , 每一个非根节点只有一个父节点。比如说这样的东西:(题外话,树这种东西最适合递归遍历了)

在这里插入图片描述

了解完了树,我们来看二叉树。与树相比,二叉树就是指每个节点最多只有两个孩子。比如上面这张图根节点A有三个子节点,节点F 有四个子节点。这肯定是不行的。我们可以将他优化成如下的二叉树(结果不唯一)。woc等等, 树变成二叉树是有要求的…. 树变成二叉树遵循这样的原则:**将节点的孩子 放在左子树;将节点的兄弟 放在右子树。**我们转换的二叉树如下:(我想掐死刚刚搞出有四个兄弟节点的自己)

在这里插入图片描述

# 在这里我觉得我有必要写一下转换树的代码,虽然很简陋但是还望包含
class Tree():
    def __init__(self):
        self.val = None
        self.son = []
    # 这个主要是为了打印我们的结果,更直观的看出转换结果的,下同
    def __str__(self):
        strings = '{' + str(self.val) + ':'
        if len(self.son) == 0:
            return str(self.val)
        for i in self.son:
            strings += str(i) + ', '
        return strings + '}'

class BinaryTree():
    def __init__(self):
        self.val = None
        self.left = None
        self.right = None
    def __str__(self):
        strings = '{' + str(self.val) + ':'
        if self.left is None and self.right is None:
            return str(self.val) 
        if self.left:
            strings += str(self.left) + ',   '
        if self.right:
            strings += str(self.right)
        return  strings   + '}'

# 利用命名空间命名我们的变量
names = locals()
listTemp = globals()
for p in 'abcdefghijk':
    names['%s' % p] = Tree()
    eval(p).val = p.upper()
# 按照最开始的树添加我们子节点的结构
a.son = [b,d,e]
b.son = [c]
d.son = [f,h]
f.son = [g,i,k,j]
# 打印结构, 确实是复合的. 这个打印有点丑,将就着看
print(a)
{A:{B:C, }, {D:{F:G, I, K, J, }, H, }, E, }

# 转换为二叉树
# 转换binary Tree
def change(node1,node2,node1_brother):
    # 子节点放左子树
    if node1.son != []:
        new_node = BinaryTree()
        new_node.val = node1.son[0].val
        node2.left = new_node
        change(node1.son[0],new_node, node1.son[1:])
    # 兄弟节点放右子树
    if len(node1_brother) != 0:
        new_node = BinaryTree()
        new_node.val = node1_brother[0].val
        node2.right = new_node
        change(node1_brother[0], new_node, node1_brother[1:])
# 根节点都是一样的
root_binary = BinaryTree()
root_binary.val = a.val
change(a,root_binary,[])

#打印结果, 和我们画的图是匹配的。成功
print(root_binary)
{A:{B:C,   {D:{F:{G:{I:{K:J}}},   H},   E}},   }

接下来到二叉查找树了。二叉查找树(Binary Search Tree,简称BST)就是在我们刚刚二叉树的基础上新增了一个条件,左节点的值小于根节点的值小于右节点的值。且所有节点的左子树和右子树页必须满足这条规律。(md这让我感觉刚刚树转成二叉树的代码白写了)这个东西我没有查到明文规范要怎么转换的,但是二叉平衡树有规范。咋实现都应该是可以的。我的实现如下,

# 直接从树转成二叉查找树
# 统计树的所有节点的值,进行排序后拿出来,我直接给后面的版本
m = 'abcdefghijk'.upper()
s = len(m)
k = s // 2
root = BinaryTree()
root.val = m[k]
def create_bst(node, list1, list2):
    # 完全基于二分查找, list1是左半边, list2 是右半边,进行递归
    if len(list1) == 0:
        node.left = None
    elif len(list1) == 1:
        new_node = BinaryTree()
        new_node.val = list1[0]
        node.left = new_node
    else:
        new_node = BinaryTree()
        s = len(list1)//2
        new_node.val = list1[s]
        node.left = new_node
        create_bst(node.left, list1[:s],list1[s+1:])
    if len(list2) == 0:
        node.right = None
    elif len(list2) == 1:
        new_node = BinaryTree()
        new_node.val = list2[0]
        node.right = new_node
    else:
        s = len(list1)//2
        new_node = BinaryTree()
        new_node.val = list2[s]
        node.right = new_node
        create_bst(node.right, list2[:s],list2[s+1:])

# 结果输出
create_bst(root, m[:k], m[k+1:])
print(root)
# 这个结果的图像放在下面了
{F:{C:{B:A,   },   {E:D,   }},   {I:{H:G,   },   {K:J,   }}}
# 这所以是这个结果(整体看起来画风偏左)是因为我用的//向下取证。如果用向上取整整体走向会偏右边

在这里插入图片描述

好的接下来就轮到我们的平衡二叉树了。平衡二叉树是在二叉树的基础上保证左右子树的高度差不大于1,而且其左右子树也必须是平衡二叉树。 为什么这么定义。我们上面的二叉查找树其实极端条件下可能会变成单链表 eg. (root)A - > (right)B -> (right) C … 很明显这种写法也是平衡二叉树… 整个一个大写的无语住了是不是。就算以我们上面的这棵树距离。虽然他现在看似是挺好的,但是我一旦插入:‘OPQRSTUVWSYZ’ 字段会发生什么呢? 我们用二分思想往里面插入如下

def Insert(node,need_input):
    if need_input > node.val:
        if node.right:
            Insert(node.right, need_input)
            
        else:
            new_node =  BinaryTree()
            new_node.val = need_input
            node.right =new_node
    if need_input < node.val:
        if node.left:
            Insert(node.left, need_input)
            
        else:
            new_node =  BinaryTree()
            new_node.val = need_input
            node.left = new_node
insert_str = 'OPQRSTUVWSYZ'
for i in insert_str:
    Insert(root,i)s
# 讲真我都不用画图,你看看后面的括号有这么多你就知道左右高度完全不匹配
{F:{C:{B:A,   },   {E:D,   }},   {I:{H:G,   },   {K:J,   {O:{P:{Q:{R:{S:{T:{U:{V:{W:{Y:Z}}}}}}}}}}}}}

这个时候有人就说,那我再遍历一次,排个序,按照上面的代码重构一颗平衡树不行么?大哥,你有没有想过我针对数据库写的树,面对经常Hash碰撞的数据量你让我遍历然后重新构建这棵树,你怕不是疯了。而平衡二叉树再插入的时候定义好了一些规则,保证每次插入的时间复杂度再log(n)的同时不会出现这种一边倒的情况。所以说, 平衡二叉树的关键不在于构建,而在于对其进行增删改的这个过程所采用的算法(进行扭转)。我们常说的平衡二叉树指AVL

AVL树为了维持自身平衡性,在插入元素时,计算了每个节点的平衡因子是否大于1,如果大于,那么就将进行相应的旋转操作以维持平衡性。原理。。怎么说的,很神奇,但是操作只有四种旋转方式,代码很简单https://blog.csdn.net/weixin_30363263/article/details/85702725,https://zhuanlan.zhihu.com/p/441805244

# 这里是伪代码
#LL 插入 (当前节点左子树的左孩子插入)
n = nownode.left.right
nownode.left.right = nownode
nownode.left = n

#RR 插入 当前节点右子树的右孩子插入, 和LL对称嘛肯定差不多的
n = nownode.right.left
nownode.right.left = nownode
nownode.right = n

# LR插入 当前节点左子树的孩子插入
先对nownode的左孩子RR旋转, 再对nownode进行LL旋转
nownode2 = nownode
nownode = nownode.left
n = nownode.right.left
nownode.right.left = nownode
nownode.right = n

n = nownode2.left.right
nownode2.left.right = nownode2
nownode2.left = n

# RL插入, LR插入的镜像
nownode2 = nownode
nownode = nownode.right
n = nownode.left.right
nownode.left.right = nownode
nownode.left = n

n = nownode2.right.left
nownode2.right.left = nownode2
nownode2.right = n

至于删除,我们再某种程度上当成插入即可。平衡二叉树之后,就是红黑树和B树了。

红黑树其实本身就是自平衡二叉树。它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。同样,红黑树也是采用节点旋转的方法保证平衡的。要想了解具体的情况,首先知道到红黑树的特性:红黑树最根本的特性就是红色和黑色 每一层所有节点都是都是同一种颜色,换句话说红黑树是由红色曾与黑色层组成的。至于为什么叫红黑树不叫其他颜色这个,俺也不清楚 (红黑树相关借鉴于https://www.cnblogs.com/skywang12345/p/3245399.html) https://www.bilibili.com/read/cv8748171

红黑树的特性:
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(空节点)是黑色。 注意:红黑树里的叶子节点是指空节点
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 保证最长的路径不会超过最短路径的二倍长

证明:最长路径不会超过最短路径的二倍长:

来张图展示以下。红黑树的旋转主要是为了完成子节点的迁移,由少的子节点向多的自己点的子树去接。

在这里插入图片描述

红黑树能够以 O(log2 n) 的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。当然,还有一些更好的,但实现起来更复杂的数据结构 能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较“便宜”的解决方案。红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。

好的压力接下来来到了 B树, B+树以及B* 树索引。B树来源于平衡二叉树,但是打破了二叉的魔咒B树以及其系列都是多叉树,经常用于查找磁盘的地址。为什么不使用二叉树了呢?因为磁盘会有大量的数据,不可能一次性把数据加载到内存中,只能注意加载磁盘页*。为了减少读写频率我们需要尽可能的降低树的高度较少IO查询的次数。这样的速度快于AVL与红黑树. B树的样子如下图

在这里插入图片描述

M阶B树的定义

  1. 每个节点最多只有m个子节点。
  2. 每个非叶子节点(除了根)具有至少⌈ m/2⌉子节点。
  3. 如果根不是叶节点,则根至少有两个子节点。
  4. 具有k个子节点的非叶节点包含k -1个键。
  5. 所有叶子都出现在同一水平,没有任何信息(高度一致)

关于B树的插入也是一个及其繁琐的过程。(真的,数据结构不如重修)

针对m阶高度h的B树,插入一个元素时,首先在B树中是否存在,如果不存在,即在叶子结点处结束,然后在叶子结点中插入该新的元素。

  • 若该节点元素个数小于m-1,直接插入;
  • 若该节点元素个数等于m-1,引起节点分裂;以该节点中间元素为分界,取中间元素(偶数个数,中间两个随机选取)插入到父节点中;
  • 重复上面动作,直到所有节点符合B树的规则;最坏的情况一直分裂到根节点,生成新的根节点,高度增加1;

(参考):https://blog.csdn.net/i_silence/article/details/108053381 这一篇讲的很详细了

删除也是, 首先查找B树中需删除的元素,如果该元素在B树中存在,则将该元素在其结点中进行删除;删除该元素后,首先判断该元素是否有左右孩子结点,如果有,则上移孩子结点中的某相近元素(“左孩子最右边的节点”或“右孩子最左边的节点”)到父节点中,然后是移动之后的情况:

  • 某结点中元素数目小于(m/2)-1,(m/2)向上取整,则需要看其某相邻兄弟结点是否丰满;
  • 如果丰满(结点中元素个数大于(m/2)-1),则向父节点借一个元素来满足条件;
  • 如果其相邻兄弟都不丰满,即其结点数目等于(m/2)-1,则该结点与其相邻的某一兄弟结点进行“合并”成一个结点。

看的出来B树已经多叉了,那B+ 树升级在了哪里呢? 我们先回顾一下B树是为了减少磁盘读写次数而存在的,那么继续优化肯定是在读写量上优化了吧?

没错,B+ 树确实在读写量上进行了优化。B+树中非叶子节点每个元素不保存数据,只用来索引。这不是妥妥的读写量飞速下降。这就意味着同样的大小的磁盘页可以容纳更多节点元素,在相同的数据量下,B+树更加“矮胖”,IO操作更少 (关于页与Mysql的页我会在后面说明),而且所有叶子节点(数据节点)本身就是一条链表,方便范围调用 https://baijiahao.baidu.com/s?id=1663349019029796519&wfr=spider&for=pc

此外再叶子节点上数据本身也依赖关键字自小尔达的顺序链接,换句话说就是有序且能顺着找到所有的数据。如图:

在这里插入图片描述

  1. 有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
  2. 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
  3. 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值