<PostgreSQL数据库内核分析>之第三章:存储管理

2024-06-08 05:18

本文主要是介绍<PostgreSQL数据库内核分析>之第三章:存储管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 一、存储管理器的体系结构
    • 二、外存管理
      • 1.表和元组的组织方式
      • 2.磁盘管理器
    • 3.内存管理

一、存储管理器的体系结构

存储管理器是DBS与物理存取设备的接口

  • 存储管理器的体系结构如下
    (1)本地内存是每个后台进程所专有,存储属于该进程的高速缓存Cache、事务管理信息、进程信息等
    (2)内存上下文:用于统一管理内存的分配和回收
    (3)PG中每个表都用一个表文件来存储,并以表的OID命名,若超出OS文件大小限制,PG会将其切分为多个文件来存储;
    每个表除了表文件外,还拥有两个附属文件:
    可见性映射表文件VM(作用:加快VACUUM的执行速度)
    空闲空间映射表文件FSM作用:(管理表文件的空闲空间)。
    在这里插入图片描述
  • 存储管理器使用了虚拟文件描述符机制VFD,使得后台进程可以打开“无限多个”文件。
  • 缓冲池:进程间共享缓冲池
    (1)为了防止多个进程并发访问共享内存中的数据时产生的冲突,PG提供了轻量级锁,用于支持对共享内存中同一数据的互斥访问
  • 缓冲池:进程私有的本地缓冲池
    (1)PG中的数据在内存中是以页面块的形式存在,每个表空间由多个BLCKSZ(一个可配置的常量)字节大小的文件块组成,每个文件块可以包含多个元组,但是PG不支持元组的跨块存储,每个元组最大为MaxHeadTupleSize,保证了每个文件块中存储的是多个完整的元组。
    (2)表文件以文件块为单位读入内存中,每一个文件块在内存中形成一个页面块。
    (3)PG在内存中开辟了缓冲区域(缓冲池):磁盘上的文件块读入内存后被存放在缓冲区中,缓冲池被划分为与文件块的尺寸相同的若干个固定大小的缓冲区,一个标准缓冲块的默认大小是8K。

一个PG进程(postmaster、postgres)从数据库读写一个元组的流程:

  • 进程的本地内存有2种Cache:存储系统表元组、存储系统表的基本信息

  • PG的共享缓冲池与本地缓冲池:本地缓冲池用于缓冲临时表数据以及其他进程不可见的数据

  • 若缓冲池中没有包含所需元组的缓冲块,则需通过存储介质管理器SMGR来从存储介质中读取,并写入到缓冲区中。

  • 磁盘管理器负责管理所有存储在磁盘上的文件的操作

  • 虚拟文件管理:虚拟文件描述符VFD机制。VFD通过合理使用有限个实际文件描述符来满足无限的VFD访问需求,VFD通过维持一个LRU池来管理FD
    在这里插入图片描述

  • 在PG中,为每个表增加了一个附属文件,即空闲空间映射表FSM,用于记录每个表文件中块的空闲空间大小,通过一定的查找机制和数据组织实现了文件块的快速选择

  • PG使用标记删除的方式来处理元组的删除,即:对元组打上删除标记,而非从物理上删除元组。
    元组的物理删除是由VACUUM机制来完成的,由于VACUUM操作时会去遍历所有文件块,去查找被删除的元组效率低,所以也为表设计了可见性映射表VM,用以加快查找的速度。

二、外存管理

每个表文件在磁盘中都以一定的结构进行存储

  • 外存管理体系结构
    在这里插入图片描述

1.表和元组的组织方式

PG中同一个表中的元组是顺序依次插入到表文件中的。

  • 元组之间不关联的表文件,称之为堆文件。
    PG包括四种堆文件:普通堆ordinary cataloged heap、临时堆temporary heap、序列sequence relation(特殊的单行表)、TOAST表(toast table)。
    (1)临时堆仅在会话过程中创建、在会话结束时自动删除;
    (2)序列是一种元组值自动增长的特殊堆
    (3)TOAST表其实也是一种普通堆,但是它被专门用于存储变长数据。
  • 堆文件的物理结构如下,
    (1)每个堆文件由多个文件块组成
    (2)Linp是ItemIdData类型的数组,每一个ItemIdData结构用来指向文件块中的一个元组
    (3)Freespace是空闲空间,新插入页面的元组都从这部分空间来分配
    (4)Special space是特殊空间,用于存放与索引方法相关的特定数据
    (5)元组信息:存放元组的实际数据和元组的头部信息(事务ID+命令ID等信息)
    在这里插入图片描述
  • 从堆中删除一个元组:采用标记删除的方法,为每个元组使用额外的数据位作为删除标记。
    当删除元组时,只需设置相应的删除标记(在元组头部,记录了删除这个元组的事务ID和命令ID),即可设置相应的删除标记。
    若上述两个ID有效,则表明该元组应该被删除。

2.磁盘管理器

磁盘管理器并非对磁盘上的文件直接进行操作,而是通过VFD机制进行文件操作。

  • 因为一个进程打开的fd数量有限(受限于OS规定的最大值),所以设计了VFD机制
  • VFD、真实文件描述符、文件之间的关系
    (1)虚拟文件描述符是指:一个叫做VFD的数据结构,其中记录了操作系统为文件分配的真实fd;
    (2)多个进程打开同一个文件,那么每个进程将获得一个真实fd,每个真实fd对应一个VFD;
    在这里插入图片描述

LRU池

  • 每一个PG后台进程都使用一个LRU(Last Recently Used,最近最少使用)池来管理所有已经打开的VFD,池中每一个VFD都对应物理上已经打开的文件;
  • 每个进程都拥有其私有的LRU池和一系列VFD,进程需要打开文件时都是从自己私有的LRU池中申请VFD
    (1)在LRU池中,使用替换最长时间未使用的VFD策略
    (2)进程在VfdCache上保持了两个链表,一个是LRU池(双向链表,通过Vfd数据结构的IruMoreRecently属性和IruLessRecently属性链接),另一个是FreeList(空闲链表,记录了所有可被分配的VFD,通过Vfd数据结构中的nextFree属性来连接)。
    (3)PG中将一个进程当前正打开的所有文件的VFD都连成一个环,即LRU池;
    每个方框代表一个VFD(一个VfdCache数组元素),方框里的符号表示该VFD在数组中的下标。
    在LRU池中,每一个VFD都通过指针链接两个VFD,通过指针IruMoreRecently链接最近最常用的VFD,通过指针IruLessRecently链接最近不长使用的VFD;
    (4)LRU池的大小与操作系统对于进程打开文件数的限制保持一致,在postmaster进程的启动过程中会调用max_safe_fds函数来检测操作系统限制;
    (5)插入:新的VFD在VFD[0]后插入;
    删除:在LRU池中删除VFD;若LRU池已满,而又要打开新的fd,则将池中末尾的VFD删除(最少使用的VFD)
    在这里插入图片描述

空闲空间映射表FSM

  • 对于每个表文件(包括系统表在内),同时创建一个名为“关系表OID_fsm”的文件,用以记录该表的空闲空间大小,称之为空闲空间映射表文件FSM。
  • FSM物理块与逻辑地址对照
    (1)为了实现快速查找,FSM文件不太大且使用了树结构;
    FSM中存储的不是实际的文件块空间的大小,而是仅用一个字节来记录,该字节的值用于描述对应文件块中空闲空间的范围:
    所以对于任意一个表块,根据该字节的值就可以知道这个表块中空闲空间的范围;
    对于任意一个表块,根据其空闲空间的大小可以计算出它对应的FSM字节的取值,eg,N字节空闲空间的表块,FSM中记录的值为(31+N)/32;
    在这里插入图片描述
    (2)FSM块之间使用了一个三层树结构:
    第0层和第1层是辅助层:快速定位满足需要表块的FSM块
    第2层FSM块:实际存放各表块的空闲空间值;
    每一个FSM快内构成一个局部的最大堆二叉树,每个叶子节点表示一个表块的空闲空间值,按照从左至右的顺序,所有第2层FSM块中的叶子节点排列起来就一一对应了表文件中的每一个表块。
    第1层FSM块中的叶子节点从左至右顺序对应第2层FSM块的跟节点,第0层与第一层关系类似。
    (3)每个FSM块大小默认是8KB,出去头部等信息,每个叶子节点用一个字节记录,假设一个FSM块内可以保存4000个叶子节点,所以,一个FSM文件(有三个FSM块:0层FSM块、1层FSM块、2层FSM块)可以记录4000^3个叶子节点(表块);
    这是远远大于2^32,单个表的最大块数(PG的块号长度为32bit,所以单个表最多只能有2^32个块)
    (4)FSM文件中第一个文件块中二叉树(0层的0号FAM块)根节点存储的是所有表块空闲空间的最大值。
    在这里插入图片描述

可见性映射表vm

  • PG为了实现多版本的并发控制,当事务删除或者更新元组时,并非从物理上删除,而是通过将其标记为无效的方式进行标记删除,最终对这些无效的清理操作则需调用VACUUM(VACUUM查找包含无效元组的文件块)
  • VACUUM有2种情况方式:快速清理Lazy VACUUM和完全清理Full VACUUM。
    vm仅在Lazy VACUUM中使用到,Full VACUUM由于要执行跨块清理等操作,需要对整个表文件进行扫描,此时vm文件作用不大。
  • vm文件也被划分为若干个文件块(vm块),vm块中除了必要的标记信息外,其他的每一位都对应一个表块,当表块中所有的元组对当前的事务都是可见的时,表块对应的位才设为1
    当对某个表块中的元组进行更新或者删除后,那么该表块在vm文件中的对应位置的标志位将被置0。
  • vm文件仅仅是作为一个提示hint,加快vacuum的速度
    在这里插入图片描述

大数据存储

  • toast机制(the oversi-attribute storage technique,数据压缩和线外存储)和大对象机制(使用一个专门的系统表来存储大对象数据)
  • toast机制
    (1)这类数据类型必须有变长(varlena类型),eg:text类型,只有在准备向支持TOAST的属性中存储超过BLOCKSZ/4字节(2KB),TOAST机制才会被触发。
    TOAST机制会试图对将要存储的数据进行压缩或线外存储数据(即:把数据存储在其他的表中),直到数据比BLOCKSZ/4字节短,或者无法得到更好的结果的时候才停止
    (2)如果一个表中的属性是可以TOAST的,那么该表的将有一个关联的TOAST表。
    (3)TOAST机制优点:可以有效地节省查询时所占的内存空间,TOAST数据只是在向用户显示结果时才被取出来,在查询过程中并不需要检查TOAST数据的具体取值
    (4)TOAST技术保障了PG中的元组大小足以存放在一个文件块中,so,TOAST技术必须被设置为一种系统自动处理的机制。
    TOAST机制主要集中于变长的数据类型。
  • 大对象
    (1)PG的大对象存储机制可以支持三种数据类型的存储:
    二进制大对象BLOB:eg:图片、视频
    字符大对象CLOB:存储大的单字节字符集数据,eg文档
    双字节字符大对象DBCLOOB:存储大的双字节字符集数据,eg变长双字节字符图形字符串
    (2)一个大对象会被分成若干个元组存放在系统表pg_largeobject中,每一个元组也称为一个页面。
    一个元组大小为2KB,设置成2KB的原因是:可以触发TOAST的压缩机制。

TOAST与对象的区别

  • TOAST是可变长数据类型的一种大数据存储机制,属于自动触发机制;
    大对象属于用于手动调用机制
  • TOAST中的数据不能丢失,一旦丢失则报错;大对象中运行数据丢失,若丢失,则用0替代;
  • 文件不适合使用TOAST技术,原因:二进制读-》将二进制当做字符串存储到变长数据类型的属性中-》转变成文件;大对象操作是将文件作为一个对象存储到大对象表中,读取时直接读取成一个文件。
  • TOAST和大对象操作保存大数据时,都是采用了将数据切片成了片段存储到表中的方式。

3.内存管理

尽可能让使用的文件停留在内存中,这样就能有效地减少磁盘I/O代价。

  • PG内存管理
    在这里插入图片描述
  • 一个内存上下文实际上就相当于一个进程环境
    PG每个子进程都拥有多个私有的内存上下文,每个子进程的内存上下文组成一个树形结构。
    在这里插入图片描述
    高速缓存
  • 当数据库访问表时,需要表的模式信息,比如表的列属性、OID、统计信息等。
    PG将表的模式信息存放在系统表中,因此要访问表,就需要首先在系统表中取得表的模式信息。
    PG设立了高速缓存Cache来提高对系统表和普通表模式的访问效率。
    Cache中包括一个系统表元组SysCache和一个表模式信息RelCache:
    SysCache主要用于缓存系统表元组;
    RelCache存放的不是元组,而是RelationData数据结构;
  • 每一个PG进程都维护着自己的SysCache和RelCache。

Cache同步

  • PG中,每一个进程都有自己的Cache。同一个进程表在不同的进程中都有对应的Cache来缓存它的元组。桶一个系统表的元组可能被多个进程的Cache所缓存,当其中某个Cache中的一个元组被删除或更新,需要通知其他进程对其Cache进行同步。
    PG中会记录下已被删除的无效元组,并通过共享消息队列的方式在进程之间传递消息,收到无效消息的进程同步地把无效元组(RelationData结构,因为RelCache缓存的是一个RelationData数据结构)才能够自己的Cache中删除。
  • 当一个元组被删除或者更新时,在同一个SQL命令的后续执行步骤中,我们依然认为该元组是有效的,直到下一个命令开始或者事务提交时改动才生效。
    在命令的边界,旧元组变为失效,同时新元组变为有效。so,当执行heap_delete或者heap_update时,不能简单地刷新cache,正确的做法是:保持一个无效链表用于记录元组的delete/update操作。
    事务完成后,将无效链表中的信息广播该事务过程中产生的无效信息,其他进程通过消息队列读取无效信息对各自的Cache进行刷新。
    当子事务提交时,只需要将该事务产生的无效消息提交到父事务,最后由最上层的事务广播无效消息。

缓冲池管理

这篇关于<PostgreSQL数据库内核分析>之第三章:存储管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1041293

相关文章

Python调用Orator ORM进行数据库操作

《Python调用OratorORM进行数据库操作》OratorORM是一个功能丰富且灵活的PythonORM库,旨在简化数据库操作,它支持多种数据库并提供了简洁且直观的API,下面我们就... 目录Orator ORM 主要特点安装使用示例总结Orator ORM 是一个功能丰富且灵活的 python O

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

使用Navicat工具比对两个数据库所有表结构的差异案例详解

《使用Navicat工具比对两个数据库所有表结构的差异案例详解》:本文主要介绍如何使用Navicat工具对比两个数据库test_old和test_new,并生成相应的DDLSQL语句,以便将te... 目录概要案例一、如图两个数据库test_old和test_new进行比较:二、开始比较总结概要公司存在多

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

查询SQL Server数据库服务器IP地址的多种有效方法

《查询SQLServer数据库服务器IP地址的多种有效方法》作为数据库管理员或开发人员,了解如何查询SQLServer数据库服务器的IP地址是一项重要技能,本文将介绍几种简单而有效的方法,帮助你轻松... 目录使用T-SQL查询方法1:使用系统函数方法2:使用系统视图使用SQL Server Configu

SQL Server数据库迁移到MySQL的完整指南

《SQLServer数据库迁移到MySQL的完整指南》在企业应用开发中,数据库迁移是一个常见的需求,随着业务的发展,企业可能会从SQLServer转向MySQL,原因可能是成本、性能、跨平台兼容性等... 目录一、迁移前的准备工作1.1 确定迁移范围1.2 评估兼容性1.3 备份数据二、迁移工具的选择2.1

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

Python中连接不同数据库的方法总结

《Python中连接不同数据库的方法总结》在数据驱动的现代应用开发中,Python凭借其丰富的库和强大的生态系统,成为连接各种数据库的理想编程语言,下面我们就来看看如何使用Python实现连接常用的几... 目录一、连接mysql数据库二、连接PostgreSQL数据库三、连接SQLite数据库四、连接Mo