postgresql 内核源码分析 btree索引插入分析,索引页面分裂流程,多举措进行并发优化,对异常进行保护处理

news2024/11/24 8:48:43

Btree索引插入流程分析

专栏内容

  • postgresql内核源码分析
  • 手写数据库toadb
  • 并发编程

开源贡献

  • toadb开源库

个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

前言

B树索引在PostgreSQL中得到了广泛应用,它是一种自平衡树数据结构,可以维护有序数据并允许进行搜索、顺序访问、插入和删除操作。在PostgreSQL中,可以在任何数据类型上使用B树索引,支持排序,支持大于、小于、等于、大于或等于、小于或等于的搜索。

B树具有一些重要的特征。首先,B树是平衡的,每个叶子页与根都由相同数量的内部页分隔开,因此搜索任何值都需要花费相同的时间。其次,B树是多分支的,每个页面通常包含许多(数百个)ctid,因此B树的深度很小,对于非常大的表,实际上可以达到4-5的深度。最后,索引中的数据按非递减顺序存储(在页面之间和每个页面内部),并且同一级别的页面通过双向列表相互连接,因此不需要每次都返回到根,可以通过遍历链表获取一个有序的数据集。

PostgreSQL中的B树索引是一种高效的数据结构,可以用于加速对有序数据的搜索和访问。通过使用B树索引,可以大大提高数据库的性能和响应时间。

概述

本文主要介绍postgresql 中常用的索引类型btree的插入过程,通过本文对postgresql 索引查询的代码有一定了解,希望能够帮助基于postgresql做内核开发的同学,当然理解也有限,正在看这部分的同学可以在评论区一起来探讨。

插入流程

在进行SQL语句 insert一条数据行时,如果某一列带有索引,那么在插入数据的同时,也需要插入一条索引项;

索引项中需要记录数据行的tid,也就是位置信息,所以插入索引的时机,是在插入数据行成功后,使用数据行的位置信息生成一条索引项,然后进行索引项的插入流程;

索引项的插入整体流程如下:

  • 生成新的索引项;
  • 检查唯一性;
  • 查找合适的插入位置;
  • 对待插入索引页面检查空闲空间是否足够;
  • 如果空间不足,则将当前索引页面进行分裂为左右两半;
  • 然后将新索引项加入左或右页面;

准备阶段

生成一个索引项,这就是需要插入的新索引项;

先是遍历btree树,从root中的元组进行二分查找,找到该索引项对应的范围所在的下层的节点的pageno,如果层级较多,每次都是如此,最终找到叶子节点,找到要插入的叶子节点的索引数据块;

检查唯一性

对于主键的列,需要检查索引的唯一性,避免插入两条相同的索引;

对于主键肯定是需要唯一的存在,为什么要在这里做唯一性检查,而不是在数据插入时检查呢?

因为在插入数据时,如果检查唯一性,还是要通过索引扫描来比对,所以直接放到索引插入阶段,当索引检查不通过时,那么前面插入的数据也是无效的。

  • 检查时,在索引树中先找到相同值的位置;
  • 然后对比它的tid是否一样,如果不一样,也就是除了之前新插入数据之外还有一条相同字段的数据存在;
  • 检查相同索引项对应的数据行的事务是否提交; 如果没有提交,则等待该事务结束,再进行检查;
  • 如果相同索引项对应的数据行的事务已经提交,那么就产生了冲突;
  • 当有冲突时,就会报错,abort当前事务,也就会导致前面插入的数据无效;

查找合适的插入位置

在前一步已经找到了符合的叶子节点,在 _bt_findinsertloc 中,还需要进一步查找合适的位置,主要有以下几种情况:

  • 对于非heapkeyspace的索引,相同键值索引项,需要插入到最右边;此时要向右查找节点;
  • 对于多个索引页面都有相同highkey,也就是重复值,那么这些页面都适合插入新值,找一个空间足够的即可;
  • 还有一种情况是,对于数据多版本需要跨页时,要新增索引项,此时索引项的值是不变的,只是对应的tid发生了变化;对于这种情况,可以先检查删除的dead索项,避免索引的分裂;

在上面向右查找的情况,postgresql做了一些优化,综合了向右找的代价,和提前分裂的代价,有概率提前分裂;

对于新插入的索引项,不能大于空闲空间的三分之一;

检查空闲空间

在 _bt_insertonpg 调用中,实现了剩余空间检查,页面分裂,索引项的真正插入;

当前查找到索引页空间小于当前新索引项时,就需要进行索引页的分裂;
如果空间足够,则直接插入即可;

页面分裂

Btree的页面分裂是插入环节中的关键点,也是难点所在;在这个环节中,因为会修改多个节点页,对并发访问影响比较大,postgresql在这里做了一些优化;

我们先按常规思路来模拟一下分裂的过程;
某一节点分裂成左右两个节点,将旧节点的内容平分到新节点中,然后变更左节点的后续链接,变更右节点的前续和后续链接,将它中加入本层的双向链表,最后将新增节点信息加到父节点中;

这样一个分裂的过程中,所有变更节点都需要加互斥锁。在btree查找章节中已经介绍过,上下层之间是单向的,也就是只能从父节点找到下层节点,为了避免重复查找父节点,在确定插入节点时,就已经将路的层次路径记了下来;

下面我们来看postgresql中如何进行分裂,以及进行了那些地方的优化;

按分裂的节点类型,分为根节点,页子节点,中间branch节点的分裂;

根页面的分裂

在一开始,数据不多时,根页面节点就足够存下了,此时根节点也是叶子节点;随着数据的增多,根节点就需要分裂了。

它的分裂不同于其它类型,下面图示分析:

在这里插入图片描述

当root节点分裂时:

1.节点分裂

  • 先加锁待分裂的root节点;
  • 在本地内存中生成一个left节点,将它的btpo_flags设置为分裂中,并且插入highkey,也就是右节点的最小值;
  • 在索引文件中生成right节点,注意这里是直接在索引文件中增加一个节点,同时加互斥锁;设置right节点的前继为待分裂的root节点,后继为空,因为它是最右的节点;
  • 将旧root中的数据根据划分位置,分别移动到左右两个节点中;并且将待插入值插入合适位置,因为是分裂,所以一定是有空闲空间的,所以在此处直接插入;
  • 将left节点拷到待分裂节点中;这样,left,right节点都在索引文件中了;
  • 将left,right页面标脏,同时更新待分裂节点的右节点的前继为right节点,并生成分裂的WAL;
  • 解锁待分裂节点的右节点;

2.更新父节点

  • 然后新创建一个root节点,将左右节点的信息插入新的root节点中;
  • 将left节点的正在分裂标志取消;
  • 新root节点页面标脏,以及生成新root页面修改的WAL;
  • 解锁root,left,right节点;

在通过pginspect插件看时,就会发现root节点的pageno随着数据增加会一直变动,其实就是树的层次在增加,每增加一层级时,就会新创建一个root页面;

叶子页面分裂

页子节点页面的分裂,是最常见的一种,当然也经过了很多的优化,比如对于相同键值的数据;还有对于多版本跨节点存储时,会再插入一条索引项,还有对于NULL值的存储;

先来看一下叶子页面分裂的整体流程示意图:

在这里插入图片描述

叶子节点分裂如下:

1.节点分裂

  • 加锁待分裂节点;
  • 在本地内存中生成一个left节点,将它的btpo_flags设置为分裂中,并且插入highkey,也就是右节点的最小值;
  • 在索引文件中生成right节点,注意这里是直接在索引文件中增加一个节点,同时加互斥锁;设置right节点的前继为待分裂的root节点,后继为空,因为它是最右的节点;
  • 将旧root中的数据根据划分位置,分别移动到左右两个节点中;并且将待插入值插入合适位置,因为是分裂,所以一定是有空闲空间的,所以在此处直接插入;
  • 将left节点拷到待分裂节点中;这样,left,right节点都在索引文件中了;
  • 将left,right页面标脏,同时更新待分裂节点的右节点的前继为right节点,并生成分裂的WAL;
  • 解锁待分裂节点的右节点;

2.更新父节点

  • 加锁待分裂节点的父节点,然后解锁右节点;这一步解锁也很关键,优化了并发性能;
  • 将右节点信息,也就是左节点的highkey值, 插入父节点中; 当然这一步可能会递归进行,因为父节点有可能还会进行分裂,直到root再次分裂,就会增加树的层次;
  • 生成父节点变动的WAL;
  • 解锁父节点,left节点;

branch页面分裂

branch节点,是一种btree中间层的节点类型,它们存储的都是下层索引节点的页面位置和对应页面上的最小值;只有叶子节点上才会存储数据页的信息;

branch节点的分裂,类似于叶子节点的分裂;

只是在更新父节点时,多了一步,将left节点的正在分裂标志取消,也就是对于中间层的分裂已经完成;

新索引项加入

如果不发生分裂,那么插入的流程如下:

  • 找到索引插入位置;
  • 索引项插入页面,并给当前页面置脏标记;
  • 更新meta页的fastroot;如果树的某一层只有一个节点时,这个节点就是fastroot,记录到meta页面,下次遍历时,直接从fastroot开始扫描;
  • 检查是否需要记录fastpath对应的块,也就是叶子节点最右节点;

索引页面插入

因为btree索引是一种有序的索引,也是存储有序的,所以增加一条索引项时,如果在插入位置没有空槽位,就需要将当前位置及后面的索引项,向后移一个槽位,再将新索引项插入;

并发控制

在整个插入过程中,查找过程,还有分裂时持有多个块的情况进行了优化;

主要通过以下措施:

扫描和插入时

这两个过程中,只会对当前页面加锁;
在索引扫描时,结束一个索引页面就会释放,再加下一个索引页面加锁,这样保持了很好的并发访问性能;
索引项只记录向下的关系,所以在扫描过程中,会记录父子关系的stack,这方便在分裂时,向上递归;

索引分裂

在分裂的时候,会加多个节点的锁,加锁原则是从左到右,避免死锁发生;

分裂时减少了加锁的节点数量,从叶子节点开始,叶子页面分裂时,会加左节点和右节点的锁;当更新父节点时,加锁父节点后,就释放了右节点的锁;整个过程中持有的锁,可能最多就是三个节点,当然还有短暂持有的锁,如meta节点,还有待分裂节点的右节点的锁,最多时会有四个节点;

fastpath

记录叶子层的rightmost,对于null直接插入还有批量顺序插入时,直接就可以从fastpath找到插入节点,也就是叶子节点的最右节点;如果不符合时,再遍历查找;
这里使用了条件锁,得不到时,也会进行遍历查找,增加并发访问性能;

fastroot

当树的某一层只有一个节点时,那这个节点就是fastroot,不用从root进行遍历,而从fastroot遍历就可以,加快速度;

对于异常的保护

因为索引页面不像数据页面是多版本机制,为了保持索引的存储精炼,而采用了原地更新,这就需要在更新时,如果服务异外宕机了,数据还能保持一致性和完整性;

在插入时的保护

如果没有发生页面分裂,在插入数据时发生了异常,此时是由WAL来恢复;WAL记录了插入的位置,以及原有数据位置的变化;

在分裂时的保护

首先在分裂时,开始只将右节点加入了索引文件,分裂节点的数据并没有发生变化;此时异常并不会影响;

接下来,分裂节点数据发生变化,此时它的flag还是正在分裂中,那么数据其实是完整的,分别在左右两个页面上,只是从父节上只能找到左节点,从左节点再顺序通过后继链表就可以找到右节点,这在前面btree索引查找时就分享过。

结尾

非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!

作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

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

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

相关文章

Swing程序设计详解(一)

【今日】 “若你决定灿烂,山无遮,海无拦” 目录 初识Swing 一 Swing简述 二 Swing常用窗体 2.1 JFrame窗体 2.2 JDialog对话框 2.3JOptionPane小型对话框 (1)通知框 (2)确认框 (3)输入框 (4)自定义对话框 三 常用布局管理器 3.1 绝…

JWT生成与解析/JWT令牌前端存储

第一步&#xff1a;创建项目 添加Maven依赖&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version> </dependency> <dependency><groupId>org.s…

【C++】深拷贝和浅拷贝 ② ( 默认拷贝构造函数是浅拷贝 | 代码示例 - 浅拷贝造成的问题 )

文章目录 一、默认拷贝构造函数是浅拷贝1、默认拷贝构造函数2、默认拷贝构造函数是浅拷贝机制 二、代码示例 - 浅拷贝造成的问题 一、默认拷贝构造函数是浅拷贝 1、默认拷贝构造函数 如果 C 类中 没有定义拷贝构造函数 , C 编译器会自动为该类提供一个 " 默认的拷贝构造函…

GeoJSON转STL:地形3D打印

我们通过将 GeoJSON 形状坐标提取到点云中并使用 Open3d 应用泊松重建&#xff0c;从 GeoJSON 数据重建 STL 网格。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 我对打印 GeoJSON 山丘的第一次尝试深感不满&#xff0c;因此想出了一个三步流程&#xff0c;仅使用开源…

Acwing 828. 模拟栈

Acwing 828. 模拟栈 题目要求思路讲解代码展示 题目要求 思路讲解 栈&#xff1a;先进后出 队列&#xff1a;先进先出 代码展示 #include <iostream>using namespace std;const int N 100010;int m; int stk[N], tt;int main() {cin >> m;while (m -- ){string o…

【JVM】经典垃圾收集器

文章目录 说明新生代收集器Serial收集器ParNew收集器Parallel Scavenge收集器 老年代收集器Serial Old收集器Parallel Old收集器CMS收集器 Garbage First收集器需要解决的问题运作过程CMS和G1的区别 说明 Java中有许多垃圾收集器&#xff08;Garbage Collector&#xff0c;GC&…

Spring Cloud Alibaba系列之nacos:(5)源码本地环境搭建

传送门 Spring Cloud Alibaba系列之nacos&#xff1a;(1)安装 Spring Cloud Alibaba系列之nacos&#xff1a;(2)单机模式支持mysql Spring Cloud Alibaba系列之nacos&#xff1a;(3)服务注册发现 Spring Cloud Alibaba系列之nacos&#xff1a;(4)配置管理 为什么要搭建本地…

范文展示,如何三步写出一篇满意的论文

第一步&#xff1a;输入文章关键信息 文章标题&#xff0c;写论文的话即为拟定的论文标题&#xff0c;例如这篇范文中的题目为“阳明心学研究” 关键词&#xff0c;可以写出多个论文主题相关的关键词&#xff0c;用逗号分开&#xff0c;例如这篇范文中只写了一个关键词“王阳…

CentOS 7.6使用mysql-8.0.31-1.el7.x86_64.rpm-bundle.tar安装Mysql 8.0

https://downloads.mysql.com/archives/community/是社区版的官网&#xff0c;可以选择版本下载。 cat /etc/redhat-release可以看到系统版本是CentOS Linux release 7.6.1810 (Core)&#xff0c;uname -r可以看到版本是3.10.0-957.el7.x86_64。 yum remove -y mysql-libs把…

计算机硬件基本组成和各硬件工作原理

计算机硬件基本组成和各硬件工作原理 计算机硬件基本组成早期冯若依曼机的结构冯若依曼机的特点 现代计算机的结构思维导图 各硬件工作原理主存储器运算器控制器I/O 计算机硬件基本组成 计算机硬件基本组成可分两大类 1.早期冯若依曼机的结构 2.现代计算机的结构 早期冯若依曼机…

类与对象的创建

package com.mypackage.oop.later;//学生类 //类里面只存在属性和方法 public class Student {//属性&#xff1a;字段//在类里面方法外面定义一个属性&#xff08;或者说是变量&#xff09;&#xff0c;然后在方法里面对他进行不同的实例化String name; //会有一个默认值&…

在word文档中找不到endnote的选项卡

本人由于在下载endnote之后才下载的office&#xff0c;所以导致在word文档中找不到endnote的选项卡&#xff0c;自己摸索到了解决方法。 首先确保已经拥有word与endnote之后&#xff0c;右键endnote打开所在文件夹&#xff1a; 在文件夹中找到这个Configure endnote.exe运行 之…

three.js简单3D图形的使用

npm init vitelatest //创建一个vite的脚手架 选择 Vanilla 之后自己处理一下 在main.js中写入 // 导入three.js import * as THREE from three// 创建场景 const scene new THREE.Scene();// 创建相机 const camera new THREE.PerspectiveCamera(45, //视角window.inner…

【Unity程序技巧】Unity中的单例模式的运用

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

el-checkbox-group限制勾选数量

<!--* Description: 视频监控 页面* Author: mhf* Date: 2023-08-15 13:26:33 --> <template><div class"videoSurveillance"><el-row :gutter"24"><el-col :span"4"><div class"videoSurveillance-left&…

亚马逊评分规则是什么,如何提高亚马逊等级评分-站斧浏览器

亚马逊平台的账户评级问题&#xff0c;如果账号评级比较差的话&#xff0c;那么会有一些不好的影响&#xff0c;因此卖家朋友们需要想办法去提升自己的账户评级。那么亚马逊评分规则是什么&#xff0c;如何提高亚马逊等级评分。 亚马逊评分规则是什么&#xff1f; 所有新卖家…

看阿里测试工程师如何玩转postman+newman+jenkins接口自动化

postman用来做接口测试非常方便&#xff0c;接口较多时&#xff0c;则可以实现接口自动化 一、环境准备 1.安装nodejs6.0 安装nodejs6.0&#xff08;github上面写的版本要求&#xff09;&#xff0c;用于安装newman4.0&#xff0c;到nodejs官网下载即可https://nodejs.org/en/…

集成Activiti-Modeler流程设计器

集成Activiti-Modeler流程设计器 Activiti Modeler 是 Activiti 官方提供的一款在线流程设计的前端插件&#xff0c;可以方便流程设计与开发人员绘制流程图&#xff0c;保存流程模型&#xff0c;部署至流程定义等等。 1、材料准备 首先我们需要获取activiti-explorer.zip&…

(三十)大数据实战——HBase集成部署安装Phoenix

前言 Phoenix 是一个开源的分布式关系型数据库查询引擎&#xff0c;它基于 Apache HBase构建。它提供了在 Hadoop 生态系统中使用 SQL查询和事务处理的能力。本节内容我们主要介绍一下Hbase如何集成部署安装Phoenix服务工具&#xff0c;并集成hive框架&#xff0c;能够快速、灵…

什么是16S rRNA,rDNA, 菌群研究为什么用16S测序,细菌如何命名分类?

谷禾健康 当谈到肠道菌群研究时&#xff0c;16S测序是一种常用的方法&#xff0c;它在了解微生物组成和多样性方面非常重要且实用。 16S rRNA是细菌和古细菌中的一个高度保守的基因片段&#xff0c;同时具有一定的变异性。通过对16S rRNA基因进行测序&#xff0c;可以确定微生物…