Mit 6.830:SimpleDB Lab5

本文详细介绍了B+树的构造原理,包括其节点类型和特性,以及查询、插入和删除操作的具体流程。在查询过程中,通过逐层比较key来定位目标叶子节点;插入时,当叶子节点满时会分裂,内部节点同样遵循此规则;删除时,通过借key或合并节点来保持结构平衡。此外,文章还提供了相关的辅助类和方法实现。

B+树

B+树

一棵 m 阶的 B+ 树最多有 m - 1 个关键字,孩子结点的个数也就是阶数

上图就是一个 4 阶的 B+树,维基百科上的定义是关键字个数比孩子节点数小 1

构造

B+数是B树的变种,是一颗多叉搜索树,一颗 n 阶的B+树主要有一下特点:

  • 每个节点至多有 n 个子女
  • 非根节点关键值个数范围: n / 2 <= k <= n - 1
  • 叶子节点间通过直接相连
  • 非叶节点不保存数据,只做索引作用(可以储存更多的指针)
  • 内部节点和父节点的 key 值不能重复,页节点与其父节点可以重复

查询

与 key 比较,逐层向下寻找

插入流程

  1. 将数据插入到叶子节点当中

  2. 判断叶子节点的 key 数量是否小于 n

    小于

    1. 结束

    大于

    1. 将该节点分裂成两个叶子节点
    2. 复制第( n / 2 )个key,将其添加到父节点(内部节点)中
  3. 判断父节点的 key 数量是否小于n

    大于

    1. 分裂父节点
    2. 将第( n / 2 )个key,添加到父节点中

分裂叶子节点(具体的内容节点)

节点中的 key 值复制到父结点中(父节点和子节点可以重复)

  • 抽出来的值相当于做一个索引,不储存相应的值

比如:

父节点       1
子节点  1 2 3 4 5 6
// 子节点此时候太多了需要分裂
父节点     1 		4
子节点   1 2 3   4 5 6

分裂内部节点(也就是索引节点)

也就是说内部节点太多,需要挤压到父节点(父节点和子节点不能够重复)

  • 原本就是索引,所以抽出来的也是索引列,可以从子节点中取出
父节点			1
子节点		2 3 4 5 6
--------------------------------
父节点		 1		4
子节点     2 3	   5 6

删除过程

规则

子节点个数大于(B+树的阶数 / 2 - 1) Math.ceil(m-1)/2 – 1 向上取整

过程

  1. 删除节点后判断是否符合规则
  2. 若兄弟节点 key 中有富余的key,向兄弟节点借一个key,同时借的key代替成为父节点的索引,否则第 3 步
  3. 若兄弟节点 key 中没有富余的key,则当前节点和兄弟节点合并成为一个新的节点,删除父节点的key,当前节点指向父节点(必为索引节点)
  4. 若索引节点的key的个数大于等于规则,结束,否则第 5 步
  5. 若兄弟节点有富余,父节点key下移,兄弟节点key上移,结束,否则第 6 步
  6. 当前节点和兄弟节点及父节点下移key合并成一个新的节点。当前节点指向父节点,重复第 4 步

PS:通过 B+ 树的删除操作后,索引节点存在的key,不一定在子节点当中存在相应的记录

前提概要

辅助类

BTreePageId:页面标识符

BTreeInternalPage、BTreeLeafPage、BTreeHeaderPage 和 BTreeRootPtrPage 对象的唯一标识符。

  • private final int tableId; // 表id
  • private final int pgNo; // 表内的页面id
  • private final int pgcateg; // 页面的类型
    • public final static int ROOT_PTR = 0;
    • public final static int INTERNAL = 1;
    • public final static int LEAF = 2;
    • public final static int HEADER = 3;

BTreeInternalPage:B+树的内部节点

BTreeInternalPage 的每个实例都存储 BTreeFile 的一页的数据,并实现 BufferPool 使用的 Page 接口。

  • private final byte[] header; //槽位占用的情况
  • private final Field[] keys; // 记录key的数组
  • private final int[] children; // 存储page的序号(PgNo),用于获取左孩子、右孩子的BTreePageId
  • private final int numSlots; // 槽数量
  • private int childCategory; // 孩子节点的类型:叶子或内部节点

BTreeLeafPage:B+树的叶节点

BTreeLeafPage 的每个实例都存储 BTreeFile 的一页的数据,并实现 BufferPool 使用的 Page 接口

  • private final byte[] header; // 槽位占用的情况
  • private final Tuple[] tuples; // 元组数组
  • private final int numSlots; // 槽数量
  • private int leftSibling; // 叶子节点(pgNo)或 0
  • private int rightSibling; // 叶子节点(pgNo)或 0

BTreeEntry:

  • private Field key; // 内部节点中的key

  • private BTreePageId leftChild; // 左孩子

  • private BTreePageId rightChild; // 右孩子

  • private RecordId rid; // 储存在页面的路径

Exercise 1

实验一针对的是搜索

BTreeFile

参数

  • private final File f; // 文件
  • private final TupleDesc td; // 元组的模式
  • private final int tableid ; // 表id
  • private final int keyField; // 索引键所在的字段

方法

  • findLeafPage:递归函数,查找并锁定 B+ 树中与可能包含关键字段 f 的最左侧页面相对应的叶子页面
    • Map<PageId, Page> dirtypages:当创建新page或更改page中的数据、指针时,需要将其添加到dirtypages中
private BTreeLeafPage findLeafPage(TransactionId tid, Map<PageId, Page> dirtypages, BTreePageId pid, Permissions perm,
                                      Field f)
            throws DbException, TransactionAbortedException {
   
   
   // some code goes here
   // 获页面类别
   int type = pid.pgcateg();
   // 叶子节点
   if(type == BTreePageId.LEAF){
   
   
      return (BTreeLeafPage) getPage(tid, dirtypages, pid, perm);
   }
   // 锁定路径上的所有内部节点,以READ_ONLY锁
   BTreeInternalPage internalPage = (BTreeInternalPage) getPage(tid, dirtypages, pid, Permissions.READ_ONLY);
   Iterator<BTreeEntry> it = internalPage.iterator();
   BTreeEntry entry = null;
   while(it.hasNext()){
   
   
      entry = it.next();
      // 如果要搜索的字段为空,找到最左节点(用于迭代器)
      if(f == null){
   
   
         return findLeafPage(tid, dirtypages, entry.getLeftChild(), perm, f);
      }
      // 如果找到的节点相等,返回
      if(entry.getKey().compare(Op.GREATER_THAN_OR_EQ, f)){
   
   
         return findLeafPage(tid, dirtypages, entry.getLeftChild(), perm, f);
      }
   }
       return findLeafPage(tid, dirtypages, entry.getRightChild(), perm, f);
}
  • getPage()

    封装锁取页面过程的方法。首先,该方法检查本地缓存(“dirtypages”),如果在那里找不到请求的页面,则从缓冲池中获取它。如果以读写权限获取页面,它还会将页面添加到dirtypages缓存中,因为据推测它们很快就会被此事务弄脏。

测试

BTreeFileReadTest.java and the system tests in BTreeScanTest.java.

Exercise 2

实验二针对的是新增节点

BTreeFile

参数

  • private final File f; // 文件
  • private final TupleDesc td; // 元组的模式
  • private final int tableid ; // 表id
  • private final int keyField; // 索引键所在的字段

方法

splitLeafPage():分裂叶子节点
	public BTreeLeafPage splitLeafPage(TransactionId tid, Map<PageId, Page> dirtypages, BTreeLeafPage page, Field field)
			throws DbException, IOException, TransactionAbortedException {
   
   
		// some code goes here
        //
        // Split the leaf page by adding a new page on the right of the existing
		// page and moving half of the tuples to the new page.  Copy the middle key up
		// into the parent page, and recursively split the parent as needed to accommodate
		// the new entry.  getParentWithEmtpySlots() will be useful here.  Don't forget to update
		// the sibling pointers of all the affected leaf pages.  Return the page into which a 
		// tuple with the given key field should be inserted.
		// 1. 获取空白的页面作为新的右页面 (叶子页面)
		BTreeLeafPage newRightPage = (BTreeLeafPage)getEmptyPage(tid, dirtypages, BTreePageId.LEAF);

		// 2. 插入当前的tuple,分割一半节点给右节点
		// 获取反向迭代器
		int tupleNum = page.getNumTuples();
		Iterator<Tuple> it = page.reverseIterator();
		for (int i = 0; i < tupleNum / 2; i++) {
   
   
			Tuple tuple = it.next();
			// 原页面删除
			page.deleteTuple(tuple);
			// 写入新页面
			newRightPage.insertTuple(tuple);
		}

		// 3. 如果当前 page 有右兄弟,连接右兄弟
		BTreePageId oldRightPageId = page.getRightSiblingId();
		// 获取页面
		BTreeLeafPage oldRightPage = oldRightPageId == null ? null : (BTreeLeafPage) getPage(tid, dirtypages
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值