我们之前学习了B树的基本原理,今天我们来看看B树的一些改良版本——B+树和B*树。如果还没有了解过的小伙伴可以点击这里:
我们首先来看看B+树:
B+树
B+树是B树的变形,是在B树基础上优化的多路平衡搜索树,B+树的规则跟B树基本类似,但是又在B树的基础上做了以下几点改进优化:
- 分支节点的子树指针与关键字个数相同
- 分支节点的子树指针p[i]指向关键字值大小在[k[i],k[i+1])区间之间
- 所有叶子节点增加一个链接指针链接在一起
- 所有关键字及其映射数据都在叶子节点出现
大致是长成这个样子的:
简单点来说,就是取消了最左边的树,让孩子等于关键字的个数,同时,所有的数据存放在叶子结点,非叶子结点只存放最小元素的地址,可以通过该地址寻找这个元素
下面是一些特点,可以结合上面的图参考:
B+树是一种专为数据库和文件系统设计的自平衡树数据结构,它具有以下几个关键特点:
- 所有数据都在叶节点:在B+树中,所有的数据记录都存储在最底层的叶节点上。内部(非叶)节点只包含索引项,每个索引项由键值和指向子节点的指针组成,而不包含数据本身。这允许内部节点存储更多的索引项,从而降低树的高度,提高查找效率。
- 叶节点相互连接:B+树的叶节点通过指针互相链接,形成一个链表。这种结构使得按顺序访问数据非常高效,有利于范围查询和数据排序。
- 自平衡:B+树在插入和删除操作时会自动重新平衡,以确保所有路径从根到叶节点的长度相同。这样可以保持树的高度较低,减少搜索深度,提高查询效率。
- 高度限制:B+树的高度通常很小,即使在处理大量数据时也是如此。这是因为每个节点可以存储多个键值和指针,这使得树的分支因子较大,从而降低了树的整体高度。
- 最小填充因子:为了保持树的平衡性和紧凑性,B+树中的节点必须至少达到某个最小填充因子,即每个节点至少应该有一半的空间被使用。当节点的键值数量低于这个阈值时,可能会发生合并或重新分配键值。
- 优化磁盘I/O:B+树被设计用于外部存储环境,如硬盘驱动器,其中磁盘访问时间远大于内存访问时间。通过将数据组织成块(节点),并且每个块尽可能多地包含数据和索引信息,B+树减少了磁盘I/O操作的次数,从而提高了性能。
- 插入和删除操作:B+树的插入和删除操作可能涉及节点的分裂和合并。当一个节点的键值数量超过其最大容量时,该节点会被分裂成两个节点;相反,如果一个节点的键值数量低于其最小填充因子,它可能会与兄弟节点合并或重新分配键值。
- 高效的范围查询:由于叶节点之间的链接,B+树可以高效地执行范围查询。只需找到范围的起始点,然后沿着叶节点链表遍历直到达到范围的终点。
这些特点使B+树成为数据库索引和其他需要高效数据检索和更新的系统中的理想选择。
B*树
B树是B+树的变形,在B+树的非根和非叶子节点再增加指向兄弟节点的指针。
下面是B树的一些特点:
- 更高的填充因子:在B*树中,每个节点的最小键值数量被设定得更高,通常是节点最大容量的一半以上。这使得树的节点更加饱满,减少了树的高度和节点分裂的次数。
- 预分配溢出:当节点的键值数量达到一定阈值(例如最大容量的三分之二)时,B*树会将新插入的键值放入该节点,但同时也会将一部分键值移动到相邻的兄弟节点,如果有的话。这种机制被称为预分配溢出,它帮助保持树的平衡,防止节点过早分裂。
- 合并和再分配:如果一个节点的键值数量低于最低阈值,B*树会尝试从相邻的兄弟节点中借用键值,或者与兄弟节点合并。这种再分配和合并的策略有助于保持树的结构更加紧密和均衡。
- 减少分裂:由于预分配溢出和更高的填充因子,B*树的节点分裂事件比普通B树要少,这减少了磁盘写操作,提高了整体性能。
- 自平衡:就像B树一样,B*树在插入和删除操作后会自动进行结构调整,以保持树的平衡,确保所有路径从根到叶节点的长度相同。
B* 树的这些特性使其在处理大量数据和频繁更新的场景下表现更好,特别是在需要优化磁盘I/O的环境中 ,如大型数据库和文件系统。然而,B*树的复杂度略高于B树,因为需要处理预分配溢出和再分配逻辑,但这通常可以通过减少磁盘写操作带来的性能提升来弥补。
总结一下三种树的特点:
B树:有序数组+平衡多叉树;
B+树:有序数组链表+平衡多叉树;
B*树:一棵更丰满的,空间利用率更高的B+树。
B树的应用
B树在计算机科学和数据管理领域有着广泛的应用,尤其是对于需要处理大量数据和频繁读写操作的系统。以下是B树及其变体(如B+树和B*树)的几个主要应用领域:
- 数据库索引:
- 数据库管理系统(DBMS)经常使用B树或其变体(如B+树)来构建索引,以加速数据的查找。B树的自平衡特性确保了所有数据访问具有相同的延迟时间,而B+树的叶节点链接则优化了范围查询和顺序访问。
- 文件系统:
- 文件系统如NTFS、EXT4、XFS等利用B树来管理磁盘上的文件和目录结构。B树的结构可以有效地处理文件系统中的元数据,如文件名、位置和权限信息。
- 主内存数据库:
- 即使在内存数据库中,B树也是组织数据和索引的常用方法之一。虽然磁盘I/O不再是瓶颈,但B树的自平衡性质仍然有助于确保数据的快速访问。
- 空间索引:
- 在地理信息系统(GIS)和地图服务中,R树(一种B树的扩展)用于空间数据的索引,如地理位置坐标,以实现高效的位置查询。
- 分布式数据库:
- 在分布式数据库系统中,B树可以帮助管理和分布跨多个节点的数据,确保数据的一致性和快速访问。
- 搜索树:
- B树可以用作一般的搜索树,用于实现字典、映射或其他需要快速查找、插入和删除操作的数据结构。
- 网络路由:
- 在某些网络路由协议中,B树可以用来存储和查找路由表信息,帮助确定数据包的最佳传输路径。
- 虚拟内存管理:
- 在操作系统中,B树可以用于虚拟内存管理,如页面表的组织,以加快虚拟地址到物理地址的转换。
- 缓存管理系统:
- 高级缓存管理系统可能使用B树来组织缓存项,以支持高效的数据查找和替换策略。
B树之所以如此流行,是因为它能够有效处理大量数据,同时保持良好的性能和数据完整性。在需要频繁读写操作的场景下,B树的自平衡和高效查找特性使其成为一个理想的选择。
我们主要介绍一下B树在MySQL中的应用:
B树在MySQL中的应用
B树在MySQL中的应用主要体现在索引上:
在这篇文章中,我们介绍过MySQL中的索引是一种数据结构,这个数据结构。没错!就是B树。
同时基于B树,在创建索引时,会有两种方式:MyISAM和InnoDB。
我们分别来介绍一下:
MyISAM
MyISAM
是 MySQL 中的一个存储引擎,它在 MySQL 的早期版本中非常流行,尤其在那些读取密集型的应用程序中,如网站和数据仓库。MyISAM 引擎具有以下特点和功能:
- 不支持事务:
- MyISAM 不提供事务支持,这意味着它不支持回滚、事务隔离级别或行级锁定。所有的操作都是立即提交的,没有事务日志,这简化了操作但也意味着数据在出现故障时可能无法恢复。
- 表级锁定:
- MyISAM 使用表级锁定,这意味着当一个查询正在更新或写入表时,其他需要读取或写入同一表的操作会被阻塞,直到锁被释放。这在高并发的写入操作场景下可能会影响性能。
- 全文索引支持:
- MyISAM 支持全文索引,这在需要执行复杂的文本搜索时非常有用。全文索引可以加速基于关键词的搜索。
- 高速读取性能:
- MyISAM 优化了读取性能,尤其在大量读取操作的场景下。由于没有事务和行级锁定的开销,读取操作通常非常快。
- 压缩和修复:
- MyISAM 支持表压缩,可以显著减小存储空间需求。此外,如果表结构损坏,可以使用
REPAIR TABLE
命令修复。
- 空间效率:
- MyISAM 引擎将数据和索引分开存储,数据存储在一个
.MYD
文件中,索引存储在一个.MYI
文件中。这种分离的存储方式在某些情况下可以提高效率。
- 不支持外键:
- MyISAM 不支持外键约束,这意味着你不能使用它来强制引用完整性。
- 缓存机制:
- MyISAM 使用操作系统缓存,而不是像 InnoDB 那样有自己的缓冲池。这在某些系统配置下可能影响性能。
- 历史和兼容性:
- MyISAM 曾经是 MySQL 的默认存储引擎,在 MySQL 5.0 之前的版本中尤为常见。然而,由于 InnoDB 的事务支持和并发性能优势,MyISAM 在较新的应用中逐渐被取代。
MyISAM的B树的叶子结点存放的是数据的地址这是一个和InnoDB最本质的区别:
上图是以以Col1为主键,MyISAM的示意图,可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。如果想在Col2上建立一个辅助索引,则此索引的结构如下图所示:
InnoDB
InnoDB
是 MySQL 数据库中最常用的存储引擎之一,尤其在需要事务处理、并发控制和数据完整性保障的应用场景中。InnoDB 被设计为一个事务安全的存储引擎,提供了高级的数据库功能,以下是 InnoDB 的一些关键特性和功能:
- 事务支持:
- InnoDB 支持 ACID(原子性、一致性、隔离性、持久性)事务特性,确保数据操作的完整性和一致性。这意味着在事务中的一系列操作要么全部成功,要么全部失败。
- 行级锁定:
- InnoDB 使用行级锁定机制,这允许并发读写操作,而不会像 MyISAM 那样导致整个表被锁定。行级锁定极大地提高了高并发环境下的性能。
- 外键支持:
- InnoDB 支持外键约束,可以用来维护表之间的关系,确保引用完整性。
- 崩溃恢复:
- InnoDB 包含崩溃恢复机制,可以自动恢复因硬件故障、系统崩溃或停电等情况导致的未完成事务,保证数据的持久性。
- 缓冲池:
- InnoDB 使用缓冲池(Buffer Pool)来缓存数据和索引,减少磁盘 I/O 操作,从而提高读写性能。
- 自适应哈希索引:
- 如果某个索引被频繁访问,InnoDB 会自动在缓冲池中创建一个哈希索引,以加速查询速度。
- 在线操作:
- InnoDB 支持在线表重命名、在线表空间重命名和在线索引操作,这意味着可以在不影响应用程序的情况下进行数据库维护。
- 插入缓冲:
- 插入缓冲技术可以提高插入性能,尤其是在批量插入操作中,通过减少磁盘写入操作。
- 支持多种索引类型:
- InnoDB 支持多种索引类型,包括 B-Tree 索引、全文索引、空间索引等。
- 数据压缩:
- InnoDB 支持行级数据压缩,可以节省存储空间,减少 I/O 操作。
- 聚簇索引:
- InnoDB 使用聚簇索引存储数据,主键索引是聚簇索引,数据行按主键顺序物理存储,这有助于加速主键查找和范围查询。
- 多版本并发控制(MVCC):
- InnoDB 实现了 MVCC,允许读取操作不会阻塞写入操作,从而提高并发性能。
由于这些特性,InnoDB 成为了现代数据库应用的首选存储引擎,尤其是在需要高并发、数据一致性和事务安全的场景下。自 MySQL 5.5 版本开始,InnoDB 已经成为了 MySQL 的默认存储引擎。
InnoDB的B树创建索引时,叶子结点存储的就是文件,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址
上图是InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录,这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。
第二个区别是,第二个区别是InnoDB的辅助索引data域存储相应记录主键的值而不是地址,所有辅助索引都引用主键作为data域。
这就是会导致一个问题标准不一样,形成的B树可能会不一样,所以InnoDB创建索引一定要你指定主键,这是因为如果你之后还有其他的寻找标准,会以当前标准在对应的B树中寻找,然后又会回到主键创建的B树中寻找,会找两次。
比如说:
CREATE TABLE students (
student_id INT AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
PRIMARY KEY (student_id), //会以student_id主键创建一棵B树
INDEX idx_name (name) //会以name创建一棵B树
) ENGINE=InnoDB;
聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。