postgres 源码解析 45 btree分裂流程_bt_split

news2024/11/25 22:48:28

B+树简介

B+树一种多路平衡树,有如下特点:

  1. m阶B+树表示每个节点最多含有m-1个元素,除了根节点之外,每个节点至少含有ceil(m/2)-1个元素。如5阶B+树,每个节点最多4个元素,除根节点之外最少含有2个元素;
  2. 内部节点不保存数据只保存索引,所有的数据保存在叶子节点中,其目的是最大化中间节点索引键数以减少树高度;
  3. 自带排序,叶子结点之间是有序的,查找的路径稳定;
  4. 插入与修改都拥有较为稳定的对数时间复杂度。叶子结点保存所有父节点的关键字记录,每次查找需要定位到叶子结点,B+树元素地插入均自底而上;
  5. 通过右指针将相邻的叶子节点连接起来,利于范围查找;
    B+树为保持平衡,需要结合自身的结构规则被打破时会进行页分裂操作,本文将结合postgres的源码来学习下PG中的btree 分裂原理。其中pg中btree在常见的btree上有所变化,相关知识见回顾:
    postgres源码解析41 btree索引文件的创建–1
    Postgresql源码(30)Postgresql索引基础B-linked-tree (引用)

关键数据结构

1 FindSplitData
该结构体记录了分裂过程中的状态信息:左页空闲空间/右页空闲空间,候选分裂点总数和当前分裂点

typedef struct
{
	/* context data for _bt_recsplitloc */
	Relation	rel;			/* index relation */
	Page		origpage;		/* page undergoing split */
	IndexTuple	newitem;		/* new item (cause of page split) */
	Size		newitemsz;		/* size of newitem (includes line pointer) */
	bool		is_leaf;		/* T if splitting a leaf page */
	bool		is_rightmost;	/* T if splitting rightmost page on level */
	OffsetNumber newitemoff;	/* where the new item is to be inserted */
	int			leftspace;		/* space available for items on left page */
	int			rightspace;		/* space available for items on right page */
	int			olddataitemstotal;	/* space taken by old items */
	Size		minfirstrightsz;	/* smallest firstright size */

	/* candidate split point data */
	int			maxsplits;		/* maximum number of splits */
	int			nsplits;		/* current number of splits */
	SplitPoint *splits;			/* all candidate split points for page */
	int			interval;		/* current range of acceptable split points */
} FindSplitData;

2 SplitPoint
该结构体记录了分裂点的一些细节信息,包括假设以此位点分裂后左页与右页的空闲空间<该信息是选择最佳分裂点的依据>,以及新插入的元组是否位于左页。

typedef struct
{
	/* details of free space left by split */
	int16		curdelta;		/* current leftfree/rightfree delta */
	int16		leftfree;		/* space left on left page post-split */
	int16		rightfree;		/* space left on right page post-split */

	/* split point identifying fields (returned by _bt_findsplitloc) */
	OffsetNumber firstrightoff; /* first origpage item on rightpage */
	bool		newitemonleft;	/* new item goes on left, or right? */
} SplitPoint;

分类策略
typedef enum
{
	/* strategy for searching through materialized list of split points */
	SPLIT_DEFAULT,				/* give some weight to truncation */
	SPLIT_MANY_DUPLICATES,		/* find minimally distinguishing point */
	SPLIT_SINGLE_VALUE			/* leave left page almost full */
} FindSplitStrat;

_bt_split

以下图为例,介绍具体执行流程,红色项为high key,待分裂页面在上层插入函数_bt_doinsert中已被排他锁锁定。
在这里插入图片描述
1 调用 _bt_findsplitloc函数确定页面分裂点;
在这里插入图片描述
2 准备临时左页
1)在本地上下文申请索引页空间 leftpage,并初始化PageHeader相关字段信息;
2)将待分裂页opage原有标识复制给leftpage,清除BTP_ROOT/BTP_SPLIT_END/BTP_HAS_GARBAGE标识位信息,新增BTP_INCOMPLETE_SPLIT标识信息(表明分裂未完成);同时将opage的页层btpo_level与前趋btpo_prev复制到leftpage对应字段;
3)将opage的 LSN 复制到leftpage,XLogInsert可能会用;
4)为 leftpage 确定high-key(右页第一项即左页的high-key),右页第一项有以下两种情况:
 (1)分裂点和插入点相等,插入项为右页第一项,即左页high-key
 (2)其他情况,分裂点处原有的项为右页第一项,即左页的high-key
5)为左页high-key做后缀截断(若需要)
 (1)首先确定左页最后一项,以便决定右页第一项中的多少个属性必须保留在 左页的新high-key中
 (2)调用_bt_truncate执行后缀截断
6)将high-key插入左页
在这里插入图片描述
3 申请右页buffer(新页调用ReadBufferExtended函数从从索引文件extend而来)

  1. 申请右页的Buffer并持有写锁,获得右页rightpage;
  2. 将临时左页 btpo_next 指向右页 rightpagenumber;
  3. 将右页 btpo_prev指向原始分裂页origpagenumber(左页最终会回写回原页)
  4. 将右页next指针指向原始页面的next指针所指的内容
  5. 获取vacuum id同时赋值给左页和右页
    6 )若原始页面不是当前层最右页面,为 rightpage 设置 high-key,即原始页面high-key
    在这里插入图片描述

4 数据分配与填充
1) 遍历旧页中的所有索引元组,根据偏序关系判断索引元组临时左页还是右页rightpage;
2) 调用_bt_pgaddtup函数将其填充至页中对应的偏移量处;
在这里插入图片描述
5 对opage的右页spage(如果有)持有写锁,更新前驱link,并为rightpage添加 BTP_SPLIT_END标识;

6 进入临界区进行写操作
1)首先将临时左页的内容复制到原旧页opage,释放临时左页占用的内存资源;
2)分别将旧页opage、右页rightpage和spage所在的缓冲区标记为脏;
3)如果分裂页即旧页不是叶子结点,则清除cbuf对应页cpage的 BTP_INCOMPLETE_SPLIT标识信息,设置该buf为脏;
4) 为上述分裂操作构建XLOG日志,重点信息包含rightpage所处层级、分裂点以及上述opage/right/spage/cpage信息;

7 清理工作
1)如果当前页不是最右页,则释放 sbuf的写锁和pin;
2) 如果当前页不是叶子结点,则释放 cbuf的写锁和pin;
3)如果是叶子结点则释放此过程申请的lefthighkey内存;
4) 最后返回右页buf;
当前页opage和右页rightpage的写锁和pin还未释放
在这里插入图片描述

zongtiliucheng图

_bt_insert_parent

通过上述流程可以发现rightpage与父页的link关系没有确定,且持有锁资源均未释放,这些操作由 _bt_insert_parent函数完成,其流程如下:
在这里插入图片描述

/*
 * _bt_insert_parent() -- Insert downlink into parent, completing split.
 *
 * On entry, buf and rbuf are the left and right split pages, which we
 * still hold write locks on.  Both locks will be released here.  We
 * release the rbuf lock once we have a write lock on the page that we
 * intend to insert a downlink to rbuf on (i.e. buf's current parent page).
 * The lock on buf is released at the same point as the lock on the parent
 * page, since buf's INCOMPLETE_SPLIT flag must be cleared by the same
 * atomic operation that completes the split by inserting a new downlink.
 *
 * stack - stack showing how we got here.  Will be NULL when splitting true
 *			root, or during concurrent root split, where we can be inefficient
 * isroot - we split the true root
 * isonly - we split a page alone on its level (might have been fast root)
 */
static void
_bt_insert_parent(Relation rel,
				  Buffer buf,
				  Buffer rbuf,
				  BTStack stack,
				  bool isroot,
				  bool isonly)
{
	/*
	 * Here we have to do something Lehman and Yao don't talk about: deal with
	 * a root split and construction of a new root.  If our stack is empty
	 * then we have just split a node on what had been the root level when we
	 * descended the tree.  If it was still the root then we perform a
	 * new-root construction.  If it *wasn't* the root anymore, search to find
	 * the next higher level that someone constructed meanwhile, and find the
	 * right place to insert as for the normal case.
	 *
	 * If we have to search for the parent level, we do so by re-descending
	 * from the root.  This is not super-efficient, but it's rare enough not
	 * to matter.
	 */
	if (isroot)
	{
		Buffer		rootbuf;

		Assert(stack == NULL);
		Assert(isonly);
		/* create a new root node and update the metapage */
		rootbuf = _bt_newroot(rel, buf, rbuf);
		/* release the split buffers */
		_bt_relbuf(rel, rootbuf);
		_bt_relbuf(rel, rbuf);
		_bt_relbuf(rel, buf);
	}
	else
	{
		BlockNumber bknum = BufferGetBlockNumber(buf);
		BlockNumber rbknum = BufferGetBlockNumber(rbuf);
		Page		page = BufferGetPage(buf);
		IndexTuple	new_item;
		BTStackData fakestack;
		IndexTuple	ritem;
		Buffer		pbuf;

		if (stack == NULL)
		{
			BTPageOpaque opaque;

			elog(DEBUG2, "concurrent ROOT page split");
			opaque = BTPageGetOpaque(page);

			/*
			 * We should never reach here when a leaf page split takes place
			 * despite the insert of newitem being able to apply the fastpath
			 * optimization.  Make sure of that with an assertion.
			 *
			 * This is more of a performance issue than a correctness issue.
			 * The fastpath won't have a descent stack.  Using a phony stack
			 * here works, but never rely on that.  The fastpath should be
			 * rejected within _bt_search_insert() when the rightmost leaf
			 * page will split, since it's faster to go through _bt_search()
			 * and get a stack in the usual way.
			 */
			Assert(!(P_ISLEAF(opaque) &&
					 BlockNumberIsValid(RelationGetTargetBlock(rel))));

			/* Find the leftmost page at the next level up */
			pbuf = _bt_get_endpoint(rel, opaque->btpo_level + 1, false, NULL);
			/* Set up a phony stack entry pointing there */
			stack = &fakestack;
			stack->bts_blkno = BufferGetBlockNumber(pbuf);
			stack->bts_offset = InvalidOffsetNumber;
			stack->bts_parent = NULL;
			_bt_relbuf(rel, pbuf);
		}

		/* get high key from left, a strict lower bound for new right page */
		ritem = (IndexTuple) PageGetItem(page,
										 PageGetItemId(page, P_HIKEY));

		/* form an index tuple that points at the new right page */
		new_item = CopyIndexTuple(ritem);
		BTreeTupleSetDownLink(new_item, rbknum);

		/*
		 * Re-find and write lock the parent of buf.
		 *
		 * It's possible that the location of buf's downlink has changed since
		 * our initial _bt_search() descent.  _bt_getstackbuf() will detect
		 * and recover from this, updating the stack, which ensures that the
		 * new downlink will be inserted at the correct offset. Even buf's
		 * parent may have changed.
		 */
		pbuf = _bt_getstackbuf(rel, stack, bknum);

		/*
		 * Unlock the right child.  The left child will be unlocked in
		 * _bt_insertonpg().
		 *
		 * Unlocking the right child must be delayed until here to ensure that
		 * no concurrent VACUUM operation can become confused.  Page deletion
		 * cannot be allowed to fail to re-find a downlink for the rbuf page.
		 * (Actually, this is just a vestige of how things used to work.  The
		 * page deletion code is expected to check for the INCOMPLETE_SPLIT
		 * flag on the left child.  It won't attempt deletion of the right
		 * child until the split is complete.  Despite all this, we opt to
		 * conservatively delay unlocking the right child until here.)
		 */
		_bt_relbuf(rel, rbuf);

		if (pbuf == InvalidBuffer)
			ereport(ERROR,
					(errcode(ERRCODE_INDEX_CORRUPTED),
					 errmsg_internal("failed to re-find parent key in index \"%s\" for split pages %u/%u",
									 RelationGetRelationName(rel), bknum, rbknum)));

		/* Recursively insert into the parent */
		_bt_insertonpg(rel, NULL, pbuf, buf, stack->bts_parent,
					   new_item, MAXALIGN(IndexTupleSize(new_item)),
					   stack->bts_offset + 1, 0, isonly);

		/* be tidy */
		pfree(new_item);
	}
}

分裂流程_bt_split本身是在插入流程_bt_insertonpg中的,而分裂后与父节点建链的插入操作依旧是用的_bt_insertonpg,这样的递归调用,保证了每次分裂的完整性。

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

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

相关文章

Java异常(Exception)处理及常见异常

很多事件并非总是按照人们自己设计意愿顺利发展的&#xff0c;经常出现这样那样的异常情况。 例如&#xff1a; 你计划周末郊游&#xff0c;计划从家里出发→到达目的→游泳→烧烤→回家。 但天有不测风云&#xff0c;当你准备烧烤时候突然天降大雨&#xff0c;只能终止郊游提…

Qt扫盲-QTableWidget理论总结

QTableWidget理论总结1. 概述2. QTableWidgetItem 概述3. 表头设置4. 常用功能5. 常用信号6. 槽函数7. 外观1. 概述 QTableWidget 是 Qt 提供的一个简单方便、标准的表格显示类。QTableWidget 中的 单元格数据 由 QTableWidgetItem 显示如果 想要一个使用你自己定义modle 的表…

MR案例(3):计算学生成绩(总分和平均分)

文章目录一、 任务目标1. 准备数据二、实行任务1. 创建Maven项目2. 添加相关依赖3. 创建日志属性文件4. 创建成绩映射器类5. 创建成绩驱动器类6. 启动成绩驱动器类&#xff0c;查看结果7. 创建成绩归并器类8. 修改成绩驱动器类9. 启动成绩驱动器列&#xff0c;查看结果一、 任务…

艾美捷利妥昔单抗Rituximab参数及应用

艾美捷利妥昔单抗Rituximab背景&#xff1a; 利妥昔单抗是一种针对CD20抗原的基因工程嵌合鼠/人单克隆IgG1κ抗体。利妥昔单抗的分子量约为145 kD。利妥昔单抗由哺乳动物细胞&#xff08;中国仓鼠卵巢&#xff09;产生。 艾美捷利妥昔单抗Rituximab基本参数&#xff1a; 中文…

Java+SSM流浪猫狗救助领养网站(含源码+论文+答辩PPT等)

项目功能简介: 该项目采用技术&#xff1a; 后端采用SSM框架 前端采用了htmlcssbootstrap框架 MySQL数据库Tomcat服务器&#xff0c;项目含有源码、文档、配套开发软件、软件安装教程、项目发布教程等 项目功能介绍&#xff1a; 项目分为前端和后端两&#xff0c;包含的功能主要…

DBCO-PEG-TPP,二苯并环辛炔-聚乙二醇-磷酸三苯酯,DBCO-PEG磷酸三苯酯

【产品描述】 西安凯新生物科技有限公司供应的&#xff1a;​DBCO-PEG-TPP亲水性聚乙二醇 (PEG) 间隔臂提供了一个长而灵活的连接&#xff0c;磷酸三苯酯为无味、无臭的白色结晶块状或粉末&#xff0c;它不溶于冷水,但可溶于50℃以上的热水中,当溶液温度降低至室温时呈现疏水性…

安装 DbVisualizer pro 10.0.16

DbVisualizer pro 10.0.16 双击安装&#xff0c; 选择下载 JRE运行后 选择 创建 桌面快捷方式 安装成功后 ping 报错无法连接到mysql &#xff0c; 驱动 所致 官方下载最新驱动 MySQL :: Download MySQL Connector/J (Archived Versions)https://downloads.mysql.com/archi…

校园二手书商城源代码基于微信小程序云开发,可在线支付提现,含详细配置教程

校园二手书商城源代码基于微信小程序云开发&#xff0c;可在线支付提现&#xff0c;含详细配置教程 完整代码下载地址&#xff1a;校园二手书商城源代码基于微信小程序云开发 欢迎使用&#xff0c;下面是配置教程 长话短说&#xff0c;下面直接说配置流程 一、小程序端 1…

JZ31. 栈的压入、弹出序列

文章目录1. 题目描述2. 解题思路3. 动图演示4. 代码实现1. 题目描述 题目链接&#xff1a;31. 栈的压入、弹出序列 2. 解题思路 元素入栈指的是把新元素放到栈顶元素的上面&#xff0c;使之成为新的栈顶元素&#xff1b; 元素出栈指的是从一个栈删除元素又称作出栈或退栈&am…

MySQL8.0高级篇(上)-架构与索引

文章目录一、MySQL环境安装与介绍1、MySQL安装1.1 安装前说明1.2 MySQL的Linux版安装1.3 MySQL登录1.4 字符集的相关操作1.5 字符集与比较规则(了解)1.6 请求到响应过程中字符集的变化1.7 SQL大小写规范1.8 sql_mode的合理设置2、MySQL的数据目录2.1 MySQL8的主要目录结构2.2 查…

Batch Normalization批量归一化

批量归一化&#xff08;batch normalization&#xff09;层&#xff0c;它能让较深的神经网络的训练变得更加容易。 对深层神经网络来说&#xff0c;即使输入数据已做标准化&#xff0c;训练中模型参数的更新依然很容易造成靠近输出层输出的剧烈变化。这种计算数值的不稳定性通…

前端基础_事件介绍

事件介绍 下面介绍一下浏览器在请求媒体数据、下载媒体数据、播放媒体数据一直到播放结束这一系列过程中&#xff0c;到底会触发哪些事件。  loadstart事件&#xff1a;浏览器开始请求媒介。  progress事件&#xff1a;浏览器正在获取媒介。  suspend事件&#xff1a;浏览器…

[附源码]计算机毕业设计Python大学生心理测评系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

(15)目标检测算法之 YOLOv7应用解析

本文翻译自&#xff1a;https://github.com/WongKinYiu/yolov7 YOLOv7 2022年发布&#xff0c;论文链接&#xff1a;YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors 模型性能如下&#xff1a; 网页端可执行demo&#xff1a;Hu…

分支和循环语句(5)

目录 1、什么是语句&#xff1f; 2、分支语句&#xff08;选择结构&#xff09; 1、if 语句 1、if 语句的语法结构 2、悬空else 3、if书写形式的对比 4、练习 3、switch语句 1、语法形式 2、在switch语句中的 break 3、default子句 4、循环语句 1、while循环 1、…

10个Excel实用操作技巧分享,使用率超高,让你一学就会

学习和工作都能用到的Excel操作技巧&#xff0c;非常简单但是很实用&#xff0c;学会之后你会发现这些技巧的使用率超高&#xff0c;整理之后放在下面了&#xff0c;都是增加工作系效率的好帮手。1.冻结窗格 如果表格记录的数据比较多&#xff0c;我们可以使用【冻结窗格】&…

A. Parsa‘s Humongous Tree(树形DP + 贪心)

Problem - 1528A - Codeforces 两个玩家正在玩一个游戏。他们有一个整数1&#xff0c;2&#xff0c;...&#xff0c;n的排列组合&#xff08;排列组合是一个数组&#xff0c;其中从1到n的每个元素正好出现一次&#xff09;。这个排列组合没有按升序或降序排序&#xff08;即排列…

智能遥测终端机——微功耗设计的物联网网关

一、产品概述 智能遥测终端机是一款采用微功耗设计的物联网网关&#xff0c;采用内置电池组供电&#xff0c;支持对模拟量、开关量、RS485传感器采集并通过NB-IoT或4G通讯方式传输数据。智能遥测终端机支持参数远程升级、远程配置、蓝牙配置等&#xff0c;智能遥测终端机集成高…

1572_AURIX_TC275_SCU中的锁步以及芯片温度采集

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这个是锁步控制寄存器&#xff0c;其实是一个控制以及状态综合的寄存器。可以读取到现在的锁步设置状态&#xff0c;也可以进行锁步的使能。 这个寄存器跟上面这一个页一样&#xff0c;只是…

开发1-5年的Java程序员,该学习哪些知识实现涨薪30K?

工作已经8年有余&#xff0c;这8年里特别感谢技术管理人员的器重&#xff0c;以及同事的帮忙&#xff0c;学到了不少东西。这8年里走过一些弯路&#xff0c;也碰到一些难题&#xff0c;也受到过做为一名开发却经常为系统维护和发布当救火队员的苦恼。遂决定梳理一下自己所学的东…