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

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.rhkb.cn/news/134010.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HCIE-HCS规划设计搭建

1、相关术语 1、等价路由 等价路由(Equal-cost routing)是一种网络路由策略,用于在网络中选择多个具有相同路由度量(路由距离或成本)的最佳路径之一来转发数据流量。 当存在多个路径具有相同的路由度量时,…

VS Code 安装方法

1.安装控制台程序.NET SDK 功能:应用能够正常的运行和构建。 .NET SDK下载地址:下载 .NET(Linux、macOS 和 Windows) 2.安装驱动编辑器vscode vscode下载地址:https://code.visualstudio.com/Download 选择System Installer,…

windows mysql8.0主从配置

windows mysql8.0主从配置 一、安装两个MySQL并配置 1. 主库配置my.ini,我的主库是安装版 [mysqld] # 设置mysql的安装目录 basedirD:\\soft\\mysql-5.7.39 # 设置mysql数据库的存放目录 datadirD:\\soft\\mysql-5.7.39\\data #设置3306端口 port3306 #主服务器…

乐趣国学—卧薪尝胆

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…

C#企业办公自动化系统asp.net+sqlserver

办公自动化网站是多层次的技术、设备和系统的综合。一个完整的办公自动化网站应包括信息的生成与输入、信息的加工与处理、信息的存储与检索、信息的复制、信息的传输与交流以及信息安全管理等功能。本软件用于构建、整合、扩展和管理企事业机构的整体信息系统,实现…

linux驱动开发day6--(epoll实现IO多路复用、信号驱动IO、设备树以及节点和属性解析相关API使用)

一、IO多路复用--epoll实现 1.核心: 红黑树、一张表以及三个接口、 2.实现过程及API 1)创建epoll句柄/创建红黑树根节点 int epfdepoll_create(int size--无意义,>0即可)----------成功:返回根节点对应文件描述符&#xf…

Elastic Stack 8.10:更简单的跨集群搜索和身份验证等等

作者:Tyler Perkins, Gilad Gal, Shani Sagiv, George Kobar, Michael Peterson, Aris Papadopoulos Elastic Stack 8.10 增强了跨集群和向量搜索、数据摄取、Kibana 和云注册。 配置远程搜索时获得更大的灵活性,并提供更多信息来分类问题,…

传统生产者和消费者问题,Sychronized版和Lock版

1.生产者和消费者问题Synchronized版 面试:单例模式、排序算法、生产者消费者、死锁 package com.kuang.pc;/*** 线程之间的通信问题,生产者和消费者问题! 等待唤醒 ,通知唤醒* 线程交替执行 A B 操作同一个变量 num0* A num1;*…

【Vue】入门及生命周期(前后端分离)

目录 一、Vue简介 1、Vue.js是什么 2、库和框架的区别 2.1 库(Library) 2.2 框架(Framework) 3、MVVM的介绍 二、Vue入门 1、Vue快速入门 2、Vue的优势 三、Vue事件 四、Vue生命周期 1、实例 一、Vue简介 1、Vue.js是什么 Vue是一款流行的构建用户界面(UI)的[渐进式…

基于 Alpine 环境构建 aspnetcore6-runtime 的 Docker 镜像

关于 Alpine Linux 此处就不再过多讲述,请自行查看相关文档。 .NET 支持的体系结构 下表列出了当前支持的 .NET 体系结构以及支持它们的 Alpine 版本。 这些版本在 .NET 到达支持终止日期或 Alpine 的体系结构受支持之前仍受支持。请注意,Microsoft 仅正…

postman导入json脚本文件(Collection v1.0、Collection v2.0)

1. 以postman v8.5.1 版本为例 2. 在postman v5.0.2 低版本中导出json脚本文件, 请选择Collection v2.0 Export - Collection v2 3. 在postman v8.5.1 版本 导入 json脚本文件 Import - Collection v2 - Export - Import

InfiniBand vs 光纤通道,存储协议的选择

数字时代,数据量爆发增长,企业越来越迫切地追求高吞吐量、低延迟和更高性能的网络基础设施,存储协议的选择变得愈发至关重要。在众多存储协议中,InfiniBand和光纤通道备受关注。本文旨在深入探讨InfiniBand和光纤通道作为存储协议…

mysql 日志总结

mysql 根据日志的功能,分6种 慢查询日志:记录所有执行时间超过 long_query_time 的所有查询,方便我们对查询进行优化通用查询日志:记录所有连接的起始时间和终止时间,以及连接发送给数据库服务器的所有指令&#xff0…

【Spring面试】二、BeanFactory与IoC容器的加载

文章目录 Q1、BeanFactory的作用是什么?Q2、BeanDefinition的作用是什么?Q3、BeanFactory和ApplicationContext有什么区别?Q4、BeanFactory和FactoryBean有什么区别?Q5、说下Spring IoC容器的加载过程(※)Q…

【Bun1.0】使用 Bun.js 构建快速、可靠和安全的 JavaScript 应用程序

bun.js Bun 是一个现代的JavaScript运行环境,如Node, Deno。主要特性如下: 启动速度快。更高的性能。完整的工具(打包器、转码器、包管理)。 官网 https://bun.sh 优点 与传统的 Node.js 不同,Bun.js 提供了一些新的特性和功…

esp32编译问题

-Werroruninitialized 显然变量是初始化了,只是这s13觉等没初始化还居然报错了。 解决方法:add_compile_options(-Wno-uninitialized) 【cmake篇】选择编译器及设置编译参数_cmake选择编译器_仲夏夜之梦~的博客-CSDN博客https://blog.csdn.net/challen…

JavaScript-promise使用+状态

Promise 什么是PromisePromise对象就是异步操作的最终完成和失败的结果&#xff1b; Promise的基本使用&#xff1a; 代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compati…

脚本:用python实现五子棋

文章目录 1. 语言2. 效果3. 脚本4. 解读5. FutureReference 1. 语言 Python 无环境配置、无库安装。 2. 效果 以第一回合为例 玩家X 玩家0 3. 脚本 class GomokuGame:def __init__(self, board_size15):self.board_size board_sizeself.board [[ for _ in range(board_…

数字IC设计之时序分析基础概念汇总

1 时钟Clock 理想的时钟模型是一个占空比为50%且周期固定的方波。时钟是FPGA中同步电路逻辑运行的一个基准。理想的时钟信号如下图: 2 时钟抖动Clock Jitter 理想的时钟信号是完美的方波&#xff0c;但是实际的方波是存在一些时钟抖动的。那么什么是时钟抖动呢?时钟抖动&#…

sql注入Less-2

后台sql s q l " S E L E C T ∗ F R O M u s e r s W H E R E i d sql "SELECT * FROM users WHERE id sql"SELECT∗FROMusersWHEREidid LIMIT 0,1"; 注入语句 http://192.168.200.26/Less-3/?id-1? union select 1,2,database();– 使用id-1 便可…